<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>리승자이의 삽질 그리고 정리</title>
    <link>https://lsj8367.tistory.com/</link>
    <description>삽질과 개념 정리를 하는 블로그
이전 블로그 (https://velog.io/@lsj8367)</description>
    <language>ko</language>
    <pubDate>Mon, 13 Apr 2026 10:24:42 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>리승자이</managingEditor>
    <item>
      <title>Mysql 인덱스 컨디션 푸시다운</title>
      <link>https://lsj8367.tistory.com/entry/Mysql-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%BB%A8%EB%94%94%EC%85%98-%ED%91%B8%EC%8B%9C%EB%8B%A4%EC%9A%B4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 회사에서 계속해서 올라오던 slow query에 대해 성능 최적화를 수행한 내용을 바탕으로 해당 포스팅을 작성한다.&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;soft delete를 사용함에 있어서 쿼리를 주의 깊게 작성해야 한다는 사실을 알고있었지만 이번 기회를 계기로 좀 더 깨달았던 것 같다.&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;pre id=&quot;code_1750850020297&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select count(*) from tables where 조건1 = ? and user_id = ? and deleted_at is null&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;유저별로 카운팅을 하지만 데이터에 맞는 카운팅 개수가 4천개를 넘어서는 순간부터 점점 응답이 느려져 4초 이상으로 가서 alert 채널에 공유가 되었다.&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;pre id=&quot;code_1750851578932&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create table test_table
(
    id           bigint auto_increment comment '아이디',
    created_at   timestamp null comment '생성일자',
    updated_at   timestamp null comment '수정일자',
    deleted_at   timestamp null comment '삭제일',
    used  tinyint(1)       null comment '사용여부',
    user_id      bigint not null comment '유저아이디',
    constraint test_table_pk
        primary key (id)
);

create index idx_user_id_used
    on test_table (user_id, used);

explain select count(*) from test_table where user_id = 1 and used is null and deleted_at is null;
explain select count(*) from test_table where user_id = 1 and used is null;&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;아래 explain 쿼리 두개를 비교해볼건데&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-06-25 오후 8.51.51.png&quot; data-origin-width=&quot;3710&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDLBND/btsOShdFir6/dIN2QpKqNsXn9szLd3UHI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDLBND/btsOShdFir6/dIN2QpKqNsXn9szLd3UHI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDLBND/btsOShdFir6/dIN2QpKqNsXn9szLd3UHI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDLBND%2FbtsOShdFir6%2FdIN2QpKqNsXn9szLd3UHI1%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;3710&quot; height=&quot;114&quot; data-filename=&quot;스크린샷 2025-06-25 오후 8.51.51.png&quot; data-origin-width=&quot;3710&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 문제가 됐던 쿼리의 실행계획을 살펴보면 Extra 열에 Using index condition; Using where&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라고 표시되는데 여기서의 Using index condition이 이번 포스팅의 핵심 주제이다.&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;h2 data-ke-size=&quot;size26&quot;&gt;Index Condition Pushdown&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/index-condition-pushdown-optimization.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1750851807516&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL :: MySQL 8.4 Reference Manual :: 10.2.1.6 Index Condition Pushdown Optimization&quot; data-og-description=&quot;10.2.1.6&amp;nbsp;Index Condition Pushdown Optimization Index Condition Pushdown (ICP) is an optimization for the case where MySQL retrieves rows from a table using an index. Without ICP, the storage engine traverses the index to locate rows in the base table and &quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/doc/refman/8.4/en/index-condition-pushdown-optimization.html&quot; data-og-url=&quot;https://dev.mysql.com/doc/refman/8.4/en/index-condition-pushdown-optimization.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/index-condition-pushdown-optimization.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/doc/refman/8.4/en/index-condition-pushdown-optimization.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL :: MySQL 8.4 Reference Manual :: 10.2.1.6 Index Condition Pushdown Optimization&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;10.2.1.6&amp;nbsp;Index Condition Pushdown Optimization Index Condition Pushdown (ICP) is an optimization for the case where MySQL retrieves rows from a table using an index. Without ICP, the storage engine traverses the index to locate rows in the base table and&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에 설명이 잘 되어있지만 내가 이해한바로 정리를 좀 해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국에 인덱스에 포함되지 않는 조건이 들어가있다고 해서 인덱스를 안타도록 쿼리를 수행하면 굉장히 비효율적으로 동작하여 조회 비용이 많이 비싸지기 때문에 먼저 인덱스를 탄 데이터들을 조회하고 여기서 불필요한 디스크 I/O를 줄인 이후에 나머지 Using where 라고 쓰여있듯, 이제부터 나머지 조건을 사용해서 데이터를 비교하여 결과를 반환하는 방식을 수행한다.&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;이렇게 많은 비용을 줄여볼 수 있는데, 문제는 4~5천건을 한번에 쿼리하는것에 있어서는 조회비용이 상당히 비싸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 내가 만든건 대략 3만건정도의 데이터를 넣고난 이후에 테스트를 했지만, 실제 운영에서 조회하는 테이블은 지금도 꾸준히 쌓이고 있지만, 개선 당시에 &lt;b&gt;대략 5억 6천만건 정도가 있었다!!!!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사실 필요없는 soft_delete 컬럼인데, 전체 테이블에 공통으로 JPA 사용하면 많이들 사용하는 BaseEntity&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기엔 id와 audit 관련 컬럼들을 모아놓고 당연하게 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기때문에 전역적으로 `@Where` 이나 `@SQLRestriction` 을 사용하여 deleted_at is null 을 넣어준게 원인이었다.&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-filename=&quot;스크린샷 2025-06-25 오후 8.55.12.png&quot; data-origin-width=&quot;3726&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NP5ql/btsOR5YOTbT/ORqthdZ1197j284lo8xnJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NP5ql/btsOR5YOTbT/ORqthdZ1197j284lo8xnJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NP5ql/btsOR5YOTbT/ORqthdZ1197j284lo8xnJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNP5ql%2FbtsOR5YOTbT%2FORqthdZ1197j284lo8xnJ0%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;3726&quot; height=&quot;150&quot; data-filename=&quot;스크린샷 2025-06-25 오후 8.55.12.png&quot; data-origin-width=&quot;3726&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되니 인덱스 내에서만 데이터를 갖고 놀고 바로 반환하기 때문에 커버링 인덱스가 적용되어 데이터 조회에 굉장히 많은 차이가 발생했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4초 -&amp;gt; 0.5초 이내로 개선&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유의미한 결과를 얻었다. 근데 대용량으로 count 되는 유저들은 따로 redis에 캐싱을 적용해서 좀 더 빠르고 db에 많이 접근하지 않게 수정하는것도 필요할 것 같다.&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;</description>
      <category>DB</category>
      <category>db</category>
      <category>ICP</category>
      <category>index condition pushdown</category>
      <category>MySQL</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/146</guid>
      <comments>https://lsj8367.tistory.com/entry/Mysql-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%BB%A8%EB%94%94%EC%85%98-%ED%91%B8%EC%8B%9C%EB%8B%A4%EC%9A%B4#entry146comment</comments>
      <pubDate>Wed, 25 Jun 2025 21:04:46 +0900</pubDate>
    </item>
    <item>
      <title>동시 차감 시 Redis Lua Script 활용하기</title>
      <link>https://lsj8367.tistory.com/entry/%EB%8F%99%EC%8B%9C-%EC%B0%A8%EA%B0%90-%EC%8B%9C-Redis-Lua-Script-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 업무하다가 동시성 이슈를 처리하기 위해 redis를 사용했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 기능 구현이 실제 업무하는 환경과 비슷한 조건이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 상금을 얻기 위해 게임 참여를 진행한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;유저는 게임에 1회 참여할때마다 공동 상금에 1원씩 적립한다.&lt;/li&gt;
&lt;li&gt;1등이 당첨되었을 때 1등은 현재까지 적립된 상금을 모두 가져간다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 2가지를 만족하는 기능 개발을 진행해야 한다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 아직까지 문제가 발생했다는것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 근데 발생할 수 있기 때문에 로직도 수정해야 하는것이 맞다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;포인트 보정 산출식 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시나리오를 하나 생각해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 A, B 가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 상금은 98원까지 적립되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;유저 A가 응모한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상금은 99원이된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;유저 B가 응모한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상금은 100원이 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;경품을 뽑는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그리기-1.jpg&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;2154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kTNR8/btsNNjj8Azm/7TlRuTPekDsicj6YY56qkK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTNR8/btsNNjj8Azm/7TlRuTPekDsicj6YY56qkK/img.jpg&quot; data-alt=&quot;동시성에 위배될 수 있는 상황&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTNR8/btsNNjj8Azm/7TlRuTPekDsicj6YY56qkK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkTNR8%2FbtsNNjj8Azm%2F7TlRuTPekDsicj6YY56qkK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;553&quot; height=&quot;714&quot; data-filename=&quot;그리기-1.jpg&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;2154&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;위의 그림처럼 A, B 둘다 1등이 당첨됐을 때의 당시 상금을 먼저 조회하기 때문에 100원을 둘다 읽는 상황이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 B는 A가 가져간 -100원 ~ B가 당첨 추출하기 이전의 상금을 가져가도록 설계해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그래서 보정값을 주는 방식으로 처음에 해결했다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터 B는 사이에 있는 금액을 어떻게 가져갈지에 대한 연산 처리방식이다.&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;B가 당시 조회한 금액 100원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis의 원자적 연산 decr 연산을 수행하여 차감한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis에는 현재 A가 상금을 가져갔으므로 (100 - A가 가져간금액 + @) 원이 있을 것이다.&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;redis 상금 - B가 조회했던 100원 = 음수값&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;그래서 조회했던 100원 - 절대값(위에서 계산한 음수값)&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;redis에 쌓인 상금이 1원이라면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 - 100 = -99&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;100 - |-99| = 1&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;B에게는 1원을 지급하고 절대값 99원을 다시 redis에 INCR 연산을 진행해준다.&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;&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 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;p data-ke-size=&quot;size16&quot;&gt;위의 로직에서도 문제가 있었던건 조회와 incr or decr 연산이 하나로 뭉쳐진 원자적 연산은 아니었다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;get의 시점과 증감하는 시점이 다르기 때문에 당연히 동시성에 위배될 수 있는 코드였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Race Condition 이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://redis.io/glossary/redis-race-condition/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://redis.io/glossary/redis-race-condition/&lt;/a&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1등 뽑기&lt;/li&gt;
&lt;li&gt;get 상금&lt;/li&gt;
&lt;li&gt;decr 상금&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;획득하는 상금이 일정 금액까지 도달하지 못했다면 뽑기를 1등이 아닌 등수가 나올때까지 재시도한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 하나로 묶여야되는데 딱 떠오르는 1가지.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산락 사용하면 1원씩 계속 넣고 빼고 상금을 처리하는데에 있어서 트래픽이 몰리면 답도 없을 것 같았다.&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;h4 data-ke-size=&quot;size20&quot;&gt;✅ Lua Script 사용하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스크립트를 사용해서 get과 decr 상금 을 엮어서 하나의 스크립트로 처리해주면 lock도 잡히지 않고 연산이 그리 복잡하지 않기 때문에 빠르게 처리해줄 수 있을 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 안에 들어가는 로직은 1등이지만 제한된 누적상금 이상이어야 당첨되도록 하는 로직만 들어가면 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 스크립트를 대략적으로 작성했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 업무코드에서는 이렇게 작성하지는 않았다.&lt;/p&gt;
&lt;pre id=&quot;code_1746633718142&quot; class=&quot;sql&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;String luaScript = &quot;&quot;&quot;
            local current_amount = tonumber(redis.call('GET', KEYS[1]))
            local prize = tonumber(ARGV[1])
            
            if current_amount == nil then
                return redis.error_reply(&quot;키가 존재하지 않습니다.&quot;)
            end
            
            if prize &amp;lt; 100 then
                return redis.error_reply(&quot;재시도&quot;)
            end
            
            if current_amount &amp;lt; prize then
                return redis.error_reply(&quot;현재 상금보다 더 많은 값을 뺄 수 없습니다.&quot;)
            end
            
            redis.call('DECRBY', KEYS[1], prize)
            return current_amount - prize

        &quot;&quot;&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;get과 decr 연산을 묶어서 수행하며 상금 금액 limit 조건만 추가 해줬다.&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;그리고 `redisTemplate.execute()` 를 활용하여 해당 lua 스크립트를 실행해주면 정상적으로 동작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;redis가 클러스터링되어 운영되고 있다면 반드시 지켜주어야한다!!&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;redis에서 싱글스레드로 동작하기에 lock보다 더 괜찮은 원자적연산으로 동시성 이슈를 해결했다!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://engineering.linecorp.com/ko/blog/atomic-cache-stampede-redis-lua-script&quot;&gt;참고자료 - Line 기술블로그&lt;/a&gt;&lt;/p&gt;</description>
      <category>NoSQL</category>
      <category>lua script</category>
      <category>redis</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/145</guid>
      <comments>https://lsj8367.tistory.com/entry/%EB%8F%99%EC%8B%9C-%EC%B0%A8%EA%B0%90-%EC%8B%9C-Redis-Lua-Script-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0#entry145comment</comments>
      <pubDate>Thu, 8 May 2025 01:23:31 +0900</pubDate>
    </item>
    <item>
      <title>트래픽이 많은 시간에 배포하여 삽질하기</title>
      <link>https://lsj8367.tistory.com/entry/Spring-DB-Replication-read-write-%EB%B6%84%EB%A6%AC%EC%8B%9C-%EB%AC%B8%EC%A0%9C%EC%A0%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 그냥 내 욕심이었는지는 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 개발한 기능중에 14시에 환율을 맞추는 게임 챌린지가 오픈되는 시간에 항상 앱 푸시 알림을 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 트래픽이 솟구치게 되는데 우리가 평상시에 운영하는 인스턴스 개수는 8개(초당 평균 300개 요청)로 운영중인데, 이 때 시간 간격 텀을 두고 우리가 설정한 최대값인 20개까(초당 호출 평균 1200회 이상)지 차오르는걸 항상 목격하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 욕심이 생겼다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 시간에도 구애받지 않고 배포하여 정상적으로 운영할 수는 없을까?&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 생각으로 부터 나와 지금부터 삽질을 시작하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서 다루는 내용은 최소한의 인프라와 그놈의 지긋지긋한 Hikari CP 얘기를 다시 한번 해보려고 한다.&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;아무것도 설정을 건드리지 않은 상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 HikariCP를 2개를 사용하는데, 1번은 Write DB, 2번은 Read DB를 물려서 사용하고 있으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`@Transactional(readOnly=true)` 일 때에 Read DB를 바라보록 AbstractRoutingDataSource 를 구성하여 바꿔줄 수 있게 세팅을했고, 이 기능이 명확하게 동작하려면 LazyConnectionDataSourceProxy로 DataSource를 한번 감싸주어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; 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;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;아무튼 아무 설정 하지 않은 상태에서 배포를 완료하면 아래 에러가 무수히 많이 20초동안 훅 찍혔다가 해소되는것을 확인했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-17 오전 12.11.01.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mwINn/btsNnJYlefD/p2KiT55OdZdAw1M9GC9Jw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mwINn/btsNnJYlefD/p2KiT55OdZdAw1M9GC9Jw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mwINn/btsNnJYlefD/p2KiT55OdZdAw1M9GC9Jw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmwINn%2FbtsNnJYlefD%2Fp2KiT55OdZdAw1M9GC9Jw0%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;1246&quot; height=&quot;164&quot; data-filename=&quot;스크린샷 2025-04-17 오전 12.11.01.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;무엇이 문제였을까?&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;HikariCP 설정이 제대로 안됐을 가능성이 있다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이전 포스팅들에서 알맞는 튜닝과 알맞는 서버리스 DB설정이 가능했고 그 부분을 해소했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;DB가 문제가되어 connection 자체에 문제가 있다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이러면 서버 자체가 뜨지 않았을 가능성이 더 컸을것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HikariCP 를 생성하는것보다 요청개수가 더 많은 경우 대기하면서 3초가 지나서 문제가 됐을 것이다.&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;해당 부분을 의심했고 이를 좀 더 자세히 보려고 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류를 찾았다!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원초적인 방법으로 쿼리를 통해 운영 DB 커넥션이 정상적으로 설정한 개수만큼 배포 완료시에 맺어지는지를 먼저 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기선 동료 개발자분이 도움을 주셨다 정말 감사합니다 !!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 나는 Write DB, 동료분은 Read DB 에 대한 개수체크를 배포를 해두고 수시로 체크했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; blue-green 배포이기 때문에 green에 문제가 생기면 바로 롤백을 하도록 했고 정확히 순간을 감지하려고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;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;아래 쿼리를 통해서 우리가 설정해준 maximum-pool-size 값까지 맞춰지는지를 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS ELB(Elastic Load Balancer) 에서 라우팅을 하기 전부터 write DB는 정상적으로 풀 생성하는것을 목격했었다.&lt;/p&gt;
&lt;pre id=&quot;code_1744816843145&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT PROCESSLIST_HOST, COUNT(*) AS CNT
FROM performance_schema.threads
GROUP BY PROCESSLIST_HOST
ORDER BY CNT DESC;&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;문제는 라우팅이 된 이후에도 Read DB에는 풀 개수가 설정값대로 생성되지 않았지만 이미 라우팅이 되어 서비스를 하기 시작한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀 개수가 모자라니 계속해서 설정한 임계치까지 풀 개수를 생성해야 하고, 쓰려고 하는 유저의 트래픽은 그 이상으로 빨리 진입했기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hikari CP 관련 에러가 무수히 많게 슉 하고 많이 떴던 것이다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 `@Transactional` 의 readOnly 속성에따라 Write/Read DB를 runtime에 동적으로 라우팅을 해줘야하기에&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;LazyConnectionDataSourceProxy, AbstractRoutingDataSource는 안쓸 수 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LazyConnectionDataSourceProxy를 쓰지 않으면 readOnly로 dataSource를 식별하기 전에 hikari에서 임의 무작위 가용가능한 dataSource를 가져오기 때문에 그렇다.&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;아래 코드로 isValid로 유효한지를 체크하면서 hikariCP 를 bean 구성때 넣어주도록 구성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1744936554610&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class ReadDataSourceInitializer {

    public ReadDataSourceInitializer(@Qualifier(&quot;secondaryDataSource&quot;) DataSource dataSources) {
        try (Connection conn = dataSources.getConnection()) {
            conn.isValid(2);
        } catch (SQLException e) {
            throw new AppApiBizException(ApiResponseCode.INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }
}&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;이 LazyConnectionDataSourceProxy를 생성하고 나서 ReadDataSourceInitializer가 생성되도록 구성했고 bean으로 등록될 때 생성자에서 바로 호출해주도록 구성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1744936605972&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Primary
  @Bean
  @DependsOn(&quot;readDataSourceInitializer&quot;)
  public DataSource dataSource(
      @Qualifier(&quot;routingDataSource&quot;) DataSource routingDataSource) {
    return new LazyConnectionDataSourceProxy(routingDataSource);
  }&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;그리고 헬스체크는 spring actuator를 사용했는데 이 기본값의 health check에서 write/read 가 서비스할 수 있는 최소 pool size 임계치를 넘어야 200OK를 주는 방식으로 HealthCheckIndicator도 같이 재구현해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1744936866680&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class HikariPoolHealthIndicator implements HealthIndicator {

  private final List&amp;lt;DataSource&amp;gt; dataSources;

  @Override
  public Health health() {
    HikariDataSource primaryHikariSource;
    HikariDataSource secondaryHikariSource;
    try {
      primaryHikariSource = dataSources.get(0).unwrap(HikariDataSource.class);
      secondaryHikariSource = dataSources.get(1).unwrap(HikariDataSource.class);
    } catch (SQLException e) {
      throw new AppApiRuntimeException(ApiResponseCode.INTERNAL_SERVER_ERROR, &quot;HikariDataSource를 찾을 수 없습니다. %s&quot;.formatted(e.getMessage()));
    }
    int primaryActiveConnections = primaryHikariSource.getHikariPoolMXBean().getActiveConnections();
    int primaryTotalConnections = primaryHikariSource.getHikariPoolMXBean().getTotalConnections();
    int secondaryActiveConnections = secondaryHikariSource.getHikariPoolMXBean().getActiveConnections();
    int secondaryTotalConnections = secondaryHikariSource.getHikariPoolMXBean().getTotalConnections();

    if (primaryTotalConnections &amp;gt;= 임계치 &amp;amp;&amp;amp; secondaryTotalConnections &amp;gt;= 임계치) {
      return Health.up()
          .withDetail(&quot;primaryHikariActive&quot;, primaryActiveConnections)
          .withDetail(&quot;primaryHikariTotal&quot;, primaryTotalConnections)
          .withDetail(&quot;secondaryHikariActive&quot;, secondaryActiveConnections)
          .withDetail(&quot;secondaryHikariTotal&quot;, secondaryTotalConnections)
          .build();
    }

    return Health.down()
        .withDetail(&quot;primaryHikariActive&quot;, primaryActiveConnections)
        .withDetail(&quot;primaryHikariTotal&quot;, primaryTotalConnections)
        .withDetail(&quot;secondaryHikariActive&quot;, secondaryActiveConnections)
        .withDetail(&quot;secondaryHikariTotal&quot;, secondaryTotalConnections)
        .withDetail(&quot;reason&quot;, &quot;Hikari Connection Pool이 전부 준비되지 않았습니다.&quot;)
        .build();
  }
}&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;아래는 run 되기 이전에 hikariCP 1 write, 2 read 가 동시에 생성되는걸 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-18 오전 9.40.03.png&quot; data-origin-width=&quot;2984&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Utti/btsNo2vRdKg/iPOcjvwuQ7CPFL0CVHz5D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Utti/btsNo2vRdKg/iPOcjvwuQ7CPFL0CVHz5D1/img.png&quot; data-alt=&quot;로컬에서 실행하여 쳐다봤던 Debug 로그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Utti/btsNo2vRdKg/iPOcjvwuQ7CPFL0CVHz5D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Utti%2FbtsNo2vRdKg%2FiPOcjvwuQ7CPFL0CVHz5D1%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;2984&quot; height=&quot;346&quot; data-filename=&quot;스크린샷 2025-04-18 오전 9.40.03.png&quot; data-origin-width=&quot;2984&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로컬에서 실행하여 쳐다봤던 Debug 로그&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;그리고 운영배포하고서 동료 개발자분하고 같이 헬스체크 이전에 hikari CP가 정상적으로 생성되어서 붙게되는지를 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나서 운영배포를 했을때 우리 배포는 blue-green이라 한번에 트래픽이 전환되는데 이때 문제 없는지를 모니터링 했었고,&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-filename=&quot;스크린샷 2025-04-18 오전 9.44.09.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RR36L/btsNpy3ryti/CvxLQ5BJUgDOM2HUr4iuZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RR36L/btsNpy3ryti/CvxLQ5BJUgDOM2HUr4iuZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RR36L/btsNpy3ryti/CvxLQ5BJUgDOM2HUr4iuZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRR36L%2FbtsNpy3ryti%2FCvxLQ5BJUgDOM2HUr4iuZ1%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;726&quot; height=&quot;270&quot; data-filename=&quot;스크린샷 2025-04-18 오전 9.44.09.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 나서 에러로그를 봤고 아무문제 없이 순탄하게 운영되는걸 확인해보고 굉장히 재밌는 경험 했다고 느꼈다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 지금 상황에서 가장 많이 트래픽이 몰리는 순간에도 배포를 했을때 지장없이 운영할 수 있도록 서버를 개선했다!!!&lt;/p&gt;</description>
      <category>Spring</category>
      <category>AbstractRoutingDataSource</category>
      <category>hikaricp</category>
      <category>LazyConnectionDataSourceProxy</category>
      <category>springboot</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/144</guid>
      <comments>https://lsj8367.tistory.com/entry/Spring-DB-Replication-read-write-%EB%B6%84%EB%A6%AC%EC%8B%9C-%EB%AC%B8%EC%A0%9C%EC%A0%90#entry144comment</comments>
      <pubDate>Fri, 18 Apr 2025 09:47:12 +0900</pubDate>
    </item>
    <item>
      <title>Redis Cache 성능 저하 이슈 수정</title>
      <link>https://lsj8367.tistory.com/entry/Redis-CacheEvict-allEntries</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번주 월요일에 슬랙에 지속적으로 같은 로그가 되게 많이 떴었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 redis를 다른 팀에서도 사용을 하고 있기에 좀 더 주의깊게 확인해봐야 했지만 전체가 태그되는 바람에 잘 모니터링 하지는 못했던 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.10.39.png&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;1612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1XHTV/btsMYM7ooED/cKTjRKf4T2H2i9zD7bC4Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1XHTV/btsMYM7ooED/cKTjRKf4T2H2i9zD7bC4Xk/img.png&quot; data-alt=&quot;당시 오류에 대한 스레드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1XHTV/btsMYM7ooED/cKTjRKf4T2H2i9zD7bC4Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1XHTV%2FbtsMYM7ooED%2FcKTjRKf4T2H2i9zD7bC4Xk%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;312&quot; height=&quot;609&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.10.39.png&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;1612&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;이게 갑자기 alert log가 뜨게 되니까 되게 이상했다. 그리고 latency가 그래프 상으로 기하급수적으로 증가한게 보일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 로그가 문제는 내가 배포했던 시간부터 쭉 나왔다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신규 기능을 해당 시간에 오픈을 진행했었고, 그 이후로 로그가 지속적으로 찍혔었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퇴근길에 놓치고 있었던 스레드에 같은 팀의 백엔드 개발자분이 먼저 남겨주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말씀주신 기능보다는 다른 기능에서 문제가 있긴 했다. 저쪽은 캐시도 사용하지 않으며 문제될것이 전혀 없던 곳이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.08.19.png&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;1084&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tj5pr/btsMW0lUSYp/rhcfOl2ykmPnOq7z0ihdZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tj5pr/btsMW0lUSYp/rhcfOl2ykmPnOq7z0ihdZ1/img.png&quot; data-alt=&quot;메인 페이지의 총 상금은 다른 key값이 있는데 slow log command가 다르다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tj5pr/btsMW0lUSYp/rhcfOl2ykmPnOq7z0ihdZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftj5pr%2FbtsMW0lUSYp%2FrhcfOl2ykmPnOq7z0ihdZ1%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;431&quot; height=&quot;555&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.08.19.png&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;1084&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;메인 페이지의 총 상금은 다른 key값이 있는데 slow log command가 다르다&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;나는 분명히 하나의 key를 잡아주고 Spring Framework의 &lt;code&gt;@Cacheable&lt;/code&gt; 을 사용해서 관리를 해주고 있는데 &lt;code&gt;keys *&lt;/code&gt; 라니?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 코드를 까봤는데 저 캐시를 사용하거나 캐시를 갱신하는 로직에서 문제가 있다고 alert을 자꾸 보내주고 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;뭐가 문제였을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 되었던 부분은 3가지였다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Cacheable을 사용하면서 key를 명시적으로 잡아주지 않았던 점&lt;/li&gt;
&lt;li&gt;CacheEvict를 사용하면서 allEntries=true 옵션을 아무생각 없이 붙였던 점&lt;/li&gt;
&lt;li&gt;RedisCacheManager 설정을 제대로 확인하지 않은점&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 3가지가 복합적으로 물리면서 문제가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SimpleKeyGenerator&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번에서 봤던 key를 명시적으로 잡아주지 않은 문제는 spring cache 자체가 Cacheable을 선언한 순간에 별도의 key 가 없다면 value + key를 하는 과정에서 key가 없기 때문에 Spring 에 임의 기본값인 SimpleKeyGenerator가 SimpleKey.EMPTY 라는 object를 반환해서 그 키값으로 해당 캐시를 넣어주게 된다. 그래서 아래처럼 키가 생성된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value 키 문자열 + ::SimpleKey []&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SimpleKey로 들어갔기 때문에 이제 임의 키로 저장되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CacheEvict allEntries 속성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 캐싱을 했으니까 조회되는데에는 기본적으로 계속 잘 읽힐텐데, 캐시 데이터를 수정해야될 때가 반드시 있을 것 아닌가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 CacheEvict를 수행하는데 위에서 key를 설정하지 않았으니 이 부분에서도 evict은 value로만 해야지 ~ 하면 이제 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;절대 안지워진다 !!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이제 이때부터 아 그럼 SimpleKey 하나만 있는데 value 하위에 있는 데이터들 모두 삭제해주면 되겠지? 라는 생각으로 allEntries=true를 적어줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javadoc을 번역해서 가져와봤다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.30.16.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/STTCq/btsMXUSRjUu/pm6ny0jUKwT3pYBZ9ZNhM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/STTCq/btsMXUSRjUu/pm6ny0jUKwT3pYBZ9ZNhM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/STTCq/btsMXUSRjUu/pm6ny0jUKwT3pYBZ9ZNhM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSTTCq%2FbtsMXUSRjUu%2Fpm6ny0jUKwT3pYBZ9ZNhM1%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;862&quot; height=&quot;238&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.30.16.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명 되어있는거 보면 key 아래의 값만 제거된다고 써있다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 진짜 웃긴데 이래놓고선 막상 동작해보면 value로 구성된 key들이 있는지 전부 탐색 한다는것이다.!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 부분에서 &lt;code&gt;keys *&lt;/code&gt; 를 사용하게 된다. 탐색해서 앞 부분이 value와 같으면 전부 삭제한다는 성능적으로 엄청 위험한 기능이었다 ㅋㅋㅋㅋㅋ  &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;code&gt;keys *&lt;/code&gt; 를 사용할게 아니니까 redis를 연결한 계정의 권한 자체에서도 keys 를 사용할 수 없게 막았어야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이쯤하고 1등에 대한 당첨 결과를 계속해서 db를 읽는건 불필요하다고 판단해서 저장하게 되었는데 여기서 1등만 저장하는것이 아니라 모든 등수 결과에 대해서 redis에 캐싱을 하고 있었다.&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;그래서 트래픽 유입이 계속해서 되고 있었기에 그들이 뽑는 개수만큼 반복적으로 CacheEvict가 호출됐고, 그에 따라서 &lt;code&gt;keys *&lt;/code&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;우선 keys를 왜 사용하게 되는지를 찾았다.&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;RedisCacheManager의 BatchStrategies 를 변경했다.&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;기존 default 로 설정하면 scan이 아닌 keys를 주입하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.40.10.png&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvti8J/btsMW0M6V3d/wqc9CTKdKz8bEDqIoLUafK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvti8J/btsMW0M6V3d/wqc9CTKdKz8bEDqIoLUafK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvti8J/btsMW0M6V3d/wqc9CTKdKz8bEDqIoLUafK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcvti8J%2FbtsMW0M6V3d%2Fwqc9CTKdKz8bEDqIoLUafK%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;1628&quot; height=&quot;350&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.40.10.png&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 scan으로 변경해줬고 1회당 최대 탐색개수를 25개로 설정해주었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.39.35.png&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOHKRQ/btsMXyP0xjM/ltmNL996jFe11ZoJlxSKy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOHKRQ/btsMXyP0xjM/ltmNL996jFe11ZoJlxSKy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOHKRQ/btsMXyP0xjM/ltmNL996jFe11ZoJlxSKy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOHKRQ%2FbtsMXyP0xjM%2FltmNL996jFe11ZoJlxSKy1%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;180&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.39.35.png&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;180&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;이렇게 해서 해결을 해주었고 key값에는 SimpleKey가 들어가지 않게 rank별로 key를 가져 적재될 수 있도록 구성했고 조회시에는 rank별로 별도 캐시 데이터를 읽어올 수 있게 구성해서 수정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.44.30.png&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCL8Su/btsMXUFmeNf/CbVwY7WXFUghawlrIvfBBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCL8Su/btsMXUFmeNf/CbVwY7WXFUghawlrIvfBBk/img.png&quot; data-alt=&quot;수정하고 리뷰 받아서 pr 병합을 했다!!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCL8Su/btsMXUFmeNf/CbVwY7WXFUghawlrIvfBBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCL8Su%2FbtsMXUFmeNf%2FCbVwY7WXFUghawlrIvfBBk%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;435&quot; height=&quot;210&quot; data-filename=&quot;스크린샷 2025-03-26 오후 9.44.30.png&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;수정하고 리뷰 받아서 pr 병합을 했다!!&lt;/figcaption&gt;
&lt;/figure&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 뭔가 잘 모르거나 실제로 데이터를 조회하거나 수정함에 있어서 이상한 명령어가 날아가지 않게 확인하려면 해당 라이브러리의 debug 로그를 로컬환경에서 찍어보면서 해야될까? 하는 생각도 잠시 들었다.&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;당장에 신규 서비스들을 개발하는 부서이기 때문에 빠르게 트래픽이 많이 유입되어 성장해서 더 깊은 고민을 많이 하는 경험도 얼른 해봤으면 좋겠다  &lt;/p&gt;</description>
      <category>NoSQL</category>
      <category>cacheevict</category>
      <category>redis</category>
      <category>Spring Framework</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/143</guid>
      <comments>https://lsj8367.tistory.com/entry/Redis-CacheEvict-allEntries#entry143comment</comments>
      <pubDate>Wed, 26 Mar 2025 21:48:06 +0900</pubDate>
    </item>
    <item>
      <title>FeignClient의 parameter type mismatch</title>
      <link>https://lsj8367.tistory.com/entry/FeignClient%EC%9D%98-parameter-type-mismatch</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다가 어드민을 사용하시는 부분에서 제보가 들어왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;request log를 보니 LocalDate 를 받도록 되어있는데 분명히 yyyy-MM-dd 형식으로 request log가 찍혔는데?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 좀 달랐다. access-language가 en-US로 되어있는것!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어드민 사용하시는 분에게 여쭤봤더니 자신은 크롬 브라우저를 영어모드로 사용한다고 했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시작점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아, 설명하기 앞서 이 상황은 앞단에 proxy 서버가 따로있다.&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;앞단은 proxy server라고 지칭하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;client -&amp;gt;&amp;nbsp; proxy server -&amp;gt; 우리의 server&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;proxy server에서는 우리 server로 bypass 시켜주며 그 때 call 해주는 방식을 Spring Cloud OpenFeign을 사용한 FeignClient호출을 해주는 것으로 설정이 되어있다.&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;근데 우리쪽 request log에서는 아래와 같은 로그가 찍혔었다.&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;a href=&quot;https://github.com/lsj8367/laboratory/tree/master/api&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;내 깃허브&lt;/a&gt;에 있다. (해당 디렉토리 내부에만 예제 코드가 있다 ㅋㅋ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 회사가 아니라 혼자서 구현하기에 Kotlin으로 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;2025-02-21T00:07:36.586+09:00 WARN 70016 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Method parameter 'date': Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDate'; Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam java.time.LocalDate] for value [2/20/25]]&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;이렇게 예외가 났고, 문자열 정보를 보면 MM/dd/YY 형식으로 나와지는걸 볼 수 있다.&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;디버깅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음은 당연히 그냥 아래처럼 데이터를 주고 보냈다.&lt;/p&gt;
&lt;pre id=&quot;code_1740064323345&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET http://localhost:8080/api/v1/test?date=2025-02-20&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;/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;이제 문제의 access-language가 들어가게 되면 그냥 오류가 나는것인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시한번 호출했다.&lt;/p&gt;
&lt;pre id=&quot;code_1740064423768&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl --location 'http://localhost:8080/api/v1/test?date=2025-02-20' \
--header 'Accept-Language: en-US,en;q=0.9'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 보냈을 때에도 정상으로 수신됐었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도대체 문제가 뭘까 하는 생각에 feign이 정말 문제라고?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 자체에 치명적인 결함이 있는건가? 했었다. -&amp;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;문제의 FeignClient&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 api 2개를 만들었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/api/v1/test&lt;/li&gt;
&lt;li&gt;/api/v1/accept&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test는 처음에 테스트를 한 이후 feign을 붙여 accept라는 api를 feign으로 연결한 api였다.&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;테스트를 만들고 호출 했을 때 accept-language 를 en-US 로 가지게끔 설정해주고, 호출을 하게되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;feign 자체에서 Spring framework 가 가지는&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GenericConversionService&lt;/li&gt;
&lt;li&gt;FormattingConversionService&lt;/li&gt;
&lt;li&gt;TemporalAccessParser&lt;/li&gt;
&lt;li&gt;ConversionUtils&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 클래스들을 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨버터와 포매터를 사용하여 spring 에서는 String으로 변환해서 주고받는 구조로 만들어 주는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;feign에서 String을 -&amp;gt; LocalDate 파싱할 때 en-US 라고 accept-language에서 locale을 설정하게 되면 이제 us 양식인 localDate 값인 dd/MM/yy 형식으로 변환이 되어 request에 저 형식 String으로 전달되게 된다.&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;그래서 Request를 이제 파싱하게 되는 Controller에 들어가기 이전에 RequestParam 이 넘어온 값을 LocalDate로 핸들링 해주어야 되는데 이미 변경된 String 값과 Locale값으로 인해 정상적인 파싱을 하지 못하고 에러가 나는 것을 볼 수 있다.&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;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Spring 에서 제공하는 컨버터를 사용해서 구현한다.&lt;/li&gt;
&lt;li&gt;전역적으로 Locale을 설정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 회사에 LocaleResolver를 구현하니 정상 동작한다고 공유했었다!&lt;/p&gt;
&lt;pre id=&quot;code_1740496848313&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
fun localeResolver(): LocaleResolver {
    return FixedLocaleResolver(Locale.KOREA)
}&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;/p&gt;</description>
      <category>디버깅</category>
      <category>accept-language</category>
      <category>FeignClient</category>
      <category>LocalDate</category>
      <category>locale</category>
      <category>Spring Cloud OpenFeign</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/142</guid>
      <comments>https://lsj8367.tistory.com/entry/FeignClient%EC%9D%98-parameter-type-mismatch#entry142comment</comments>
      <pubDate>Wed, 26 Feb 2025 00:22:18 +0900</pubDate>
    </item>
    <item>
      <title>Hikari CP Connection is not available 오류</title>
      <link>https://lsj8367.tistory.com/entry/Hikari-CP-Connection-is-not-available-1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;일을 하면서 서버 에러로그에 언제부턴가&lt;br /&gt;&lt;code&gt;HikariPool-1 - Connection is not available, request timed out after 3000ms&lt;/code&gt;&lt;br /&gt;위와 같은 에러 문구가 특정 주기를 가지고 계속 노출되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 여러가지 문제가 있었는데, 먼저 해결된 것부터 써보려고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HikariCP pool size&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 내가 개발하는 서비스 특성상 특정 시간대 or CRM으로 푸시알림이 발송된 순간에 트래픽이 몰리는 경향이 있다.&lt;br /&gt;그래서 내가 입사하기 이전부터 서비스에선 hikari cp 권장사항인 maximum-pool-size와 minimum-idle 개수를 다르게 줬었다.&lt;br /&gt;물론 minimumIdle 설정 개수가 더 적었었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 잘 유지되다가 어느날부터 트래픽이 몰리면 이제 유휴에서 maximum개수가 될 때까지 connection pool을 만들게되는데 이 때 만드는 속도보다 커넥션을 더 많이 필요로 하게되면 그때부터 지연 응답이 되면서 해당 에러로그가 집계 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초창기보다는 트래픽이 어느정도 늘었기 때문에 내가 당시에 &lt;a href=&quot;https://github.com/brettwooldridge/HikariCP?tab=readme-ov-file&quot;&gt;HikariCP Docs&lt;/a&gt; 를 읽고서 아래 이미지 처럼 쓰여있길래 일단 pool size 개수를 maximum, minimum 개수를 하나로 맞추자고 했었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dun6zE/btsMo3o0KQ8/94KB6TFFecEuwbyUEzhth1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dun6zE/btsMo3o0KQ8/94KB6TFFecEuwbyUEzhth1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dun6zE/btsMo3o0KQ8/94KB6TFFecEuwbyUEzhth1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdun6zE%2FbtsMo3o0KQ8%2F94KB6TFFecEuwbyUEzhth1%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; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 개선하고 난 뒤에는 에러로그가 해소되었었다!!!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서버가 시작하자마자 바로 엄청난 트래픽이 호출되었을 때&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 이 부분은 인프라 설정과도 관련이 있을 수 있는데, 톰캣 서버가 시작된 이후에 우린 spring actuator가 제공해주는 &lt;code&gt;/actuator/health&lt;/code&gt; 를 통해 AWS ECS에서 헬스체크를 하고있다.&lt;br /&gt;이 때 health check 성공 갯수의 호출 간격이 되게 짧았어서 성공하면 이제 connection pool은 1개씩 애플리케이션이 구동되고 생성되는데, 이 pool size가 다 차지 않았음에도 서비스를 시작하는 경우에도 마찬가지로 같은 에러로그가 나왔었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 뭐 따로 우리쪽에서 해결할 것이 없이 인프라팀에 요청드려서 health check 간격을 좀 더 늘려서 해달라고 요청을 드렸었고, 이 이후에는 해당 설정 관련으로 에러로그가 따로 나오지는 않았던 것 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아직 해결하지 못한 AWS Aurora Mysql Serverless 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 계속해서 &lt;b&gt;아직도 발생하고 있다!!!&lt;/b&gt;&lt;br /&gt;회사의 DBA분도 같이 계속해서 봐주고계신데, 아무리 생각해도 설정상 문제는 없는데 어떤 특정 시간대에 마구잡이로 위에서 나왔던 에러로그가 무수히 많이 찍혔었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음으로 DB자체를 의심했던 순간이었다.&lt;br /&gt;애초에 serverless로 db를 바꾸면서 갑자기 에러로그가 나오는 것을 보고 혹시나 하면서 우리 팀 내 개발자분들도 전부 DB를 의심했다.&lt;br /&gt;그래서 처음에 DBA분도 aws에 문의를 해봐야겠다고 해서 질문을 드린후 이런 답변이 왔었다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;이전에 설명드린 대로 해당 이슈는 innodb buffer pool resize 과정에서 hash resize가 발생하여 wait/synch/sxlock/innodb/hash_table_lock 대기 이벤트가 발생했고, 이로 인해 고객님의 serverless cluster에서 hash resize 과정이 완료될때 까지 connection 이슈가 지속 되었던 것으로 확인됩니다.

현재 고객님이 사용하시는 serverless cluster는 mysql_aurora.3.06.1 버전을 사용 하시는 것으로 확인됩니다. 
Aurora mysql  3.08 버전부터 &quot;확장 또는 축소 이벤트 중에 잠금 해시 테이블의 크기 조정이 길어져 발생하는 재시작 문제&quot;가 해결되어, hash resize로 인한 재시작 및 connection 이슈가 완화된 것으로 확인됩니다. [+]
해당 serverless cluster를 mysql_aurora 3.08 이상 버전으로의 업그레이드를 고려해 보시는 것을 권해드립니다. 
[+] Aurora MySQL 데이터베이스 엔진 업데이트 2024-11-18(버전 3.08.0, MySQL 8.0.39와 호환) - https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraMySQLReleaseNotes/AuroraMySQL.Updates.3080.html 


고객님께서 aurora mysql의 버전 업그레이드가 상황적으로 어려우시다면, 다음과 같은 사항들을 점검해보시는 것을 권해드립니다.
▼ hash_table_lock 대기 이벤트는 주로 세 가지 이유로 발생할 수 있습니다. [+]
(1). 버퍼 풀의 크기가 너무 작아서 자주 접근하는 페이지를 모두 메모리에 유지할 수 없을 때
(2). 워크로드가 많아서 데이터 페이지가 자주 제거되고 다시 로드될 때
(3). 버퍼 풀에서 페이지를 읽는 중에 오류가 발생할 때 (이는 데이터 손상을 의미할 수 있습니다)
[+] synch/sxlock/innodb/hash_table_locks - https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/ams-waits.sx-lock-hash-table-locks.html 


현재 고객님의 serverless 클러스터는 minAcu 0.5, maxAcu 45로 설정되어 있는 것으로 확인됩니다. serverless 클러스터는 워크로드 변화에 따라 자동으로 scailing 되는데, 이 과정에서 minAcu과 maxAcu 값, 그리고 실제 워크로드 양에 따라 scailing의 규모가 달라집니다. 
많은 워크로드가 갑자기 들어오면 innodb buffer pool의 대규모 resize가 발생하고, 이때 hash resize도 함께 일어나게 됩니다. 따라서 우선적으로 워크로드를 점검해 보시는 것을 권해드립니다.

고객님께서는 performance insight를 사용 중이시므로, minAcu를 2.0 이상으로 올리시는 것을 권장드립니다. 또한, 고객님의 환경에서 테스트를 통해 워크로드 양에 따른 Acu 최적화도 진행해 보시는 것을 권해드립니다. [+]
[+] 클러스터에 대한 최소 Aurora Serverless v2 용량 설정 선택 - https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.setting-capacity.html#aurora-serverless-v2.viewing.performance-insights &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 그 당시에 답변이 왔었고, 결국에 우리 버전이 3.08 보다 낮은 버전을 사용했었고, 이 때 serverless mysql이 트래픽이 많이 몰리는 순간 스케일 아웃을 진행할 때 innodb buffer pool의 resize가 발생하니까 이 때 순단이 될텐데, 커넥션을 이 당시에 맺을 수 없으니까 애플리케이션 입장에서는 커넥션을 당연히 사용할 수 없는 상황이고 그에 따라서 connection 이슈가 슈루루룩 하고 쌓였던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때마다 고객들에게 서비스를 제대로 제공하지 못하는 것에 나는 굉장히 조금 아쉬웠었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직도 이 minAcu 라는 값은 회사 내에서 계속 적정 값을 찾아서 튜닝을 하고 있는 중이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 서비스를 잘 운영하다가도 이벤트로 트래픽이 확 증가하는 순간에는 미리 예측한 모수정도를 위한 서비스 증설, db 사양 반영을 한다면 어느정도 대응은 가능하겠지만 의도치 않은 or 이벤트를 의도했지만 예상한것보다 더 많이 들어오는 경우는 무조건적으로 이렇게 간헐적인 에러가 나고, 그 에러를 해소하기 위해 별도의 사양 증설은 무조건적으로 필요한 것 같다.&lt;br /&gt;100% 막을 수 있다면 너무 좋겠지만? 이 나름대로 해소하는 재미가 있다고 생각한다.&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번외 : 앞으로 블로그 좀 더 잘쓰고 많이 쓰려고 노력해야겠다. 너무 놀았던것 같다 ㅋㅋㅋㅋ  &lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>DB</category>
      <category>hikaricp</category>
      <category>MySQL</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/141</guid>
      <comments>https://lsj8367.tistory.com/entry/Hikari-CP-Connection-is-not-available-1#entry141comment</comments>
      <pubDate>Wed, 19 Feb 2025 22:12:31 +0900</pubDate>
    </item>
    <item>
      <title>2024년 회고</title>
      <link>https://lsj8367.tistory.com/entry/2024%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
      <description>&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;1. 이직&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 회사에서 자비스앤빌런즈(삼쩜삼)로 이직하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 회사에 비해서 인원이 일단 굉장히 많아졌고 그리고 개발자분들이 되게 많아서 놀랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 나머지 회사들이 아깝게 떨어져서 마지막으로 여길 오게된건 운명이 이끈걸까? 싶다.&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;- 카카오뱅크 (과제 불합)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 토스뱅크 (2차면접 불합 -&amp;gt; 재시도 후 1차면접 불합)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카카오페이증권 (과제 취소) -&amp;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;결과가 그렇게 막 좋다라고 볼 수는 없지만, 이전에 비해서는 많이 나아진것 같다고 생각하고 있다.&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;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;이전 회사에서는 많아야 n만건(n &amp;lt;= 100,000) 정도인 데이터만 처리했다면, 계속해서 유저분들이 많이 사용해주시면서 1억건 이상의 데이터를 보면서 성능 튜닝을 한 경험도 가지게 되어서 만족스러웠다.&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;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타 부서의 기존 구성되어있는 로직을 알 수 없어 뭘 모르는지 모르는 두루뭉술한 설명&lt;/li&gt;
&lt;li&gt;모르는걸 모른다고 말할 수 있는 자신감&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지내면서 시간이 해결해주겠지 하고 넘기곤 했는데, 점심 식사를 언제 한번 하면서 조금은 더 노력해보면 좋겠다는 피드백도 주셨던 분이 계셨어서 소프트 스킬을 계속해서 개선하고 발전시켜야겠다는 생각을 지속적으로 하게되었다!!&lt;/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;/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;e.g) 인터페이스와 추상클래스의 차이 등&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;처음은 가벼운 질문으로 시작해서 이 사람이 어디까지 고민해봤는가? or 고민해보지 않았다면 지금이라도 생각해봤을 때 어떤 방식으로 했으면 좋겠는지? 같은 내용들을 협업하는 관점에서 물어봐주시고 핑퐁을 하는 과정이 너무 재밌었고, 좋았던 시간이었다.&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;3. 간단하게 월별정리&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1~3월초&lt;/h4&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;h4 data-ke-size=&quot;size20&quot;&gt;3월중 ~ 6월&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이직한 회사에서의 수습기간 생활이었다.&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;그리고 문화생활도 지원되고 식대도 제공되기 때문에 너무 좋았다.(한국인은 역시 밥심인걸까? 아무튼 이게 너무 좋았다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아! 그리고 이 때부터 사이클에 재미를 느끼기 시작했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구따라 시작했던 사이클인데 친구는 접고 이제는 나혼자 쭉 타게된...ㅋㅋㅋ 그러면서 길을 알고싶어 동호회 활동도 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세상은 넓었고, 고수는 많았다. 뒤에 따라가며 많은 체력을 키웠던 것 같다! ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면서 뭔가 고수가 되고 싶어서 이 때부터 집부터 회사까지 25km 정도되는 거리를 자전거로 출퇴근 하기 시작했었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20241230_223613214.jpg&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xIZMe/btsLATVU7ba/bWNiqtYGTDelBlraAMec8k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xIZMe/btsLATVU7ba/bWNiqtYGTDelBlraAMec8k/img.jpg&quot; data-alt=&quot;처음 출퇴근 했던 기록이다...!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xIZMe/btsLATVU7ba/bWNiqtYGTDelBlraAMec8k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxIZMe%2FbtsLATVU7ba%2FbWNiqtYGTDelBlraAMec8k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;747&quot; data-filename=&quot;KakaoTalk_20241230_223613214.jpg&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;1440&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;이 출퇴근 이후로 다시 검진을 받아보니 그런것들이 싹 사라져있었다! 그래서 겨울이라 지금은 못타지만 다시 25년 3월부터는 또 계속해서 자전거를 탈 생각이다.&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;7월 ~ 10월&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 내가 우리 앱에있는 커뮤니티 그리고 만보기 서비스 등등이 우리 앱에 붙게 되어 너무 재밌게 개발했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 입사했던 초반보다 점점 사용해주시는 유저분들이 많아져서 tps가 전보다 늘어나는걸보고 너무 좋아했던게 기억난다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 10월이 지나면서 이제 부서가 변경된다고 얘기를 들었던 것 같다.&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;이런데서 회식하는걸 처음 경험해봤다.. ㅋㅋㅋ 확실히 뭔가 큰 회사에 매출도 잘 나고 있어서 그런가 이런게 되게 좋았다.&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;그리고 굉장히 바쁜 나날을 지속했던 것 같다. 이 때 퇴근하고서는 잇잇(Eat-it) 이라는 푸드트럭 연계 주문 서비스 앱의 서버개발을 맡아서 3인이 한팀이 되어서 개발을 또 했었다. 각기 다른 회사 그리고 대학생분 까지 계셨어서 같은 기능이지만 정말 많은 경우의 수가 있구나를 깨달으며 다른 분들이 구성해주신 코드를 보면서 또 많이 학습했던 시즌이었다.&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;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;10월 ~ 12월&lt;/h4&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;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;그리고 12월부터는 회사 일이 바빠지기 시작해서 사이드로 앱 개발하던 잇잇을 내가 탈퇴하게 된다.&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;그리고 DDD를 좀 더 명확하게 이해하고 이 부분을 공유하면 회사 백엔드 개발자분들에게 도움이 될 수 있을까 해서 스터디를 참여하게 됐다. 내년 1월부터 시작할 예정이다.&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;그리고 글을 쓰는 지금도 12월이니까!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 내년부터면 나도 독립해서 서울에서 출퇴근을 할 것 같다. 나이도들고 체력이 좀 안좋아진 것인지 점점 왕복 3시간이라는 출퇴근 시간이 굉장히 힘들어져서 원룸이더라도 월세가 조금은 비싸더라도 이 시간을 줄이고 뭐라도 의미있는걸 하자! 라는 생각이 강해서 이제는 혼자 살아보자 라고 생각했던 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올 한해도 내 나름대로 고생한 것 같고, 내년에도 더 고생해서 내가 목표하는 곳 까지 쭉쭉 나아갈 수 있었으면 좋겠다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 뭐든 잘할필요 없이 뭘 한다고 맘먹었다면 꾸준하기만해도 성공할 수 있을 것 같다는 생각도 들고, 그래서 포기하지 않고 중꺾마 유지하면 난 잘될거라고 믿고있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 내년부터는 3시간 출퇴근 시간이 30분 이내로 줄어들 예정이라서 벌어들인 2시간 30분 정도를 개발공부를 하는데에 시간을 더 쓰는 그런 2025년이 됐으면 좋겠다. 잘가라 2024년!&lt;/p&gt;</description>
      <category>Diary</category>
      <category>회고</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/140</guid>
      <comments>https://lsj8367.tistory.com/entry/2024%EB%85%84-%ED%9A%8C%EA%B3%A0#entry140comment</comments>
      <pubDate>Mon, 30 Dec 2024 22:53:24 +0900</pubDate>
    </item>
    <item>
      <title>DataIntegrityViolationException에 대해서</title>
      <link>https://lsj8367.tistory.com/entry/DataIntegrityViolationException</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;짧은 근황을 먼저 얘기하자면...&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;나중에 다른 포스팅으로 해당 부분은 잘 작성해보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅인 &lt;a href=&quot;https://lsj8367.tistory.com/entry/Transactional-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B3%A0%EC%93%B0%EA%B8%B0&quot;&gt;@Transactional 제대로 알고쓰기&lt;/a&gt; 에서는 무지성 중첩 트랜잭션에 대한 포스팅이었는데,&lt;br /&gt;이번에는 좀 다른 케이스였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 예외가 발생하는 것이었다.&lt;br /&gt;회사에서는 Mysql을 RDBMS로 채택하여 사용해서 아래와 같은 예외가 보였지만, 예제에서는 H2 DB를 사용하기 때문에 같은 에러메시지는 아니지만,&lt;br /&gt;내용은 똑같다는 점을 일단 알고 넘어가면 좋을 것 같다.&lt;/p&gt;
&lt;pre class=&quot;vhdl&quot;&gt;&lt;code&gt;HHH000099: an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in com.lsj8367.github.DemoEntity entry (don't flush the Session after an exception occurs)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 예외가 나서 에러 로그에 StackTrace를 같이 찍어주고 있었기 때문에 지켜보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 예제를 구현한 것은 &lt;a href=&quot;https://github.com/lsj8367/laboratory/tree/master/jpa&quot;&gt;깃허브&lt;/a&gt;에 있다.&lt;br /&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;구성하기 좋게 Facade -&amp;gt; Service -&amp;gt; Repository 로 단방향으로 흐르는 구조로 구성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 코드와는 완전히 다르고 정말 예제만을 위해 이렇게 구성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-20 오후 11.05.33.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ra3D3/btsI8SlbARB/aBhuJR2squtuIQMkgyqe91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ra3D3/btsI8SlbARB/aBhuJR2squtuIQMkgyqe91/img.png&quot; data-alt=&quot;DemoFacade&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ra3D3/btsI8SlbARB/aBhuJR2squtuIQMkgyqe91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRa3D3%2FbtsI8SlbARB%2FaBhuJR2squtuIQMkgyqe91%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;663&quot; height=&quot;548&quot; data-filename=&quot;스크린샷 2024-08-20 오후 11.05.33.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DemoFacade&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-20 오후 11.06.36.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL9SMx/btsI81vpOot/kw7hAwuEWRUnDhZJADJEu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL9SMx/btsI81vpOot/kw7hAwuEWRUnDhZJADJEu0/img.png&quot; data-alt=&quot;DemoService&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL9SMx/btsI81vpOot/kw7hAwuEWRUnDhZJADJEu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL9SMx%2FbtsI81vpOot%2Fkw7hAwuEWRUnDhZJADJEu0%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;653&quot; height=&quot;442&quot; data-filename=&quot;스크린샷 2024-08-20 오후 11.06.36.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DemoService&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-filename=&quot;스크린샷 2024-08-20 오후 11.07.07.png&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzI0On/btsI85xRvE5/aUdGaPdK2Z3Zu4f4NkLcBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzI0On/btsI85xRvE5/aUdGaPdK2Z3Zu4f4NkLcBk/img.png&quot; data-alt=&quot;DemoEntity&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzI0On/btsI85xRvE5/aUdGaPdK2Z3Zu4f4NkLcBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzI0On%2FbtsI85xRvE5%2FaUdGaPdK2Z3Zu4f4NkLcBk%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;599&quot; height=&quot;264&quot; data-filename=&quot;스크린샷 2024-08-20 오후 11.07.07.png&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DemoEntity&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;이제 Service Layer의 save 로직은 name 값엔 &quot;name&quot;을 code값엔 &quot;code&quot; 를 무조건적으로 저장하도록 구성했고, entity 조건엔 unique 제약조건으로 code 값을 설정해주었다.&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회는 저장이 정상 수행되며, 2회 수행시에는 uk 조건을 만족하지 못해 예외가 발생할 것이다.&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-filename=&quot;스크린샷 2024-08-20 오후 11.08.59.png&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BNomF/btsI99eJKZX/Wv6DVMhtqwK10ebDaO72Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BNomF/btsI99eJKZX/Wv6DVMhtqwK10ebDaO72Lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BNomF/btsI99eJKZX/Wv6DVMhtqwK10ebDaO72Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBNomF%2FbtsI99eJKZX%2FWv6DVMhtqwK10ebDaO72Lk%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;3444&quot; height=&quot;96&quot; data-filename=&quot;스크린샷 2024-08-20 오후 11.08.59.png&quot; data-origin-width=&quot;3444&quot; data-origin-height=&quot;96&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;중복 예외인 DataIntegrityViolationException 을 catch 절에서 잡고있는데 어떻게 된 것일까?&lt;br /&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 부분을 알기 위해서는 JPA를 사용할 당시에 1차캐시를 기억해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차 캐시는 말 그대로 영속성 컨텍스트에 해당 엔티티 값들을 저장하는 방식인데, 우리는 당연스레 쿼리를 나가는 것을 생각하지 않으니 저장 객체를 핸들링 하는 것처럼 코드를 작성하게 되는데 이부분에서 문제가 발생했던 것이다.&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;일단 당연한 얘기지만 @Transactional 내부에 있기 때문에 로직들은 전부 트랜잭션이 끝나는 부분에 맞춰서 쿼리가 나갈 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 우리는 DataIntegrityViolationException을 catch로 잡아줘서 커스텀하게 던져주는 IllegalArgumentException은 잡히지 않는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-20 오후 11.15.15.png&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brNoDT/btsJanRmexj/B0Y1o7zKYokMeRtnPNSKAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brNoDT/btsJanRmexj/B0Y1o7zKYokMeRtnPNSKAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brNoDT/btsJanRmexj/B0Y1o7zKYokMeRtnPNSKAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrNoDT%2FbtsJanRmexj%2FB0Y1o7zKYokMeRtnPNSKAk%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;994&quot; height=&quot;618&quot; data-filename=&quot;스크린샷 2024-08-20 오후 11.15.15.png&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-20 오후 11.15.29.png&quot; data-origin-width=&quot;2908&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nTN7w/btsI85xRAUY/b0zad2Vk7jwGFlZeZ3wRx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nTN7w/btsI85xRAUY/b0zad2Vk7jwGFlZeZ3wRx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nTN7w/btsI85xRAUY/b0zad2Vk7jwGFlZeZ3wRx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnTN7w%2FbtsI85xRAUY%2Fb0zad2Vk7jwGFlZeZ3wRx1%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;2703&quot; height=&quot;45&quot; data-filename=&quot;스크린샷 2024-08-20 오후 11.15.29.png&quot; data-origin-width=&quot;2908&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 IllegalArgumentException을 던지는 것으로 체크가 되지 않는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그러면 왜 때문에 DataIntegrityViolationException이 중간에 발생했을까?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 사항은 위의 코드에선 flush() 를 명시적으로 호출해주는 경우에 발생했다. (실제 쿼리가 이때 반영되기 때문이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면서 ExceptionHandler를 통해 500예외를 핸들링하는 방식으로 구현되었다.&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;실제 업무 로직에서는 findAll을 해주는 쿼리가 같이 들어있었기 때문에 이 로직이 실행되기 이전에 flush를 수행하여 save가 제대로 반영되지 않고 DataIntegrityViolationException이 발생하게 된 것이다.&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;이를 예방하기 위해서는 너무 DB레이어의 UK 제약조건만 믿고 try-catch만을 심어서 데이터를 체크해주는 그런 부분을 지양해야 된다고 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1. 반드시 중복 데이터가 들어가지 못하게 만드는 기능이라면, 이전에 이미 등록된 데이터가 있는지 여부를 확인하는 로직을 넣어주는 것도 좋은 구성인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 만약에 db uk 예외가 발생하는 경우라면 @Transactional 을 적재적소에 알맞는 위치에 넣어 구성해주고, 그 @Transactional 을 사용하는 부분에 정말로 try-catch가 필요한지 다시한번 생각해볼 필요가 있다. (무지성 트랜잭셔널 금지!!)&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;</description>
      <category>JPA</category>
      <category>DataIntegrityViolationException</category>
      <category>JPA</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/139</guid>
      <comments>https://lsj8367.tistory.com/entry/DataIntegrityViolationException#entry139comment</comments>
      <pubDate>Tue, 20 Aug 2024 23:21:25 +0900</pubDate>
    </item>
    <item>
      <title>Mysql 인덱스</title>
      <link>https://lsj8367.tistory.com/entry/%EC%9D%B8%EB%8D%B1%EC%8A%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스란?&lt;br /&gt;번역을하면 바로 색인이라는 단어로 번역된다.&lt;br /&gt;색인은, 검색하면 책속의 낱말이나 어떤 챕터나 구절들을 빠르게 찾아볼 수 있게 쪽수 정보를 나타내주는 것을 뜻한다.&lt;br /&gt;이 개념을 DB에 적용시킨 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스는 어떻게 구성하는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 CREATE INDEX 키워드로 구성이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Team 이라는 테이블이 이렇게 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;id(primary key)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;name&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;member_id&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;grade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;홍길동&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;가나다&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;고길동&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;나길동&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름만으로 인덱스를 구성하고 싶다면 아래와 같이 수행해주면 된다.&lt;br /&gt;&lt;code&gt;CREATE INDEX &amp;lt;인덱스명&amp;gt; 테이블(칼럼);&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE INDEX team_index team(name);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해주게 되면 name의 오름차순 순서로 정렬되게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 정렬이 되는가?&lt;br /&gt;기본적으로 Mysql에서는 BTree 자료구조로 인덱스를 구성하게 된다.&lt;br /&gt;b트리는 이진트리와 같게 기본적으로 정렬을 통해 구성해주게 된다.&lt;br /&gt;-&amp;gt; 데이터 탐색에 용이하도록 구성하는 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 생성하면 아래와같이 주소값을 참조하고 있는 구성이 완료된다.&lt;br /&gt;| name | 주소값 |&lt;br /&gt;|------|-----------------------|&lt;br /&gt;| 팀1 | name이 팀1인 어떤 데이터의 주소값 |&lt;br /&gt;| 팀2 | name이 팀2인 어떤 데이터의 주소값 |&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되었을 때 다시 SELECT 쿼리를 수행하게되면,&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT * FROM team WHERE name = '팀1';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name만을 인덱스 구성한 인덱스 주소 구성을 바라보게 될 것이다.&lt;br /&gt;여기서 이제 트리구조의 탐색 알고리즘이 뒷받침하여 탐색하게 되는데,&lt;br /&gt;가장 가운데 row부터 맞는지 검색을 들어가서 팀1 조건에 부합하는 데이터를 찾게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾게되면 name이 &lt;code&gt;팀1&lt;/code&gt; 인 데이터가 예시에선 지금 2개로 구성했으니, 2개만을 조회해서 결과 반환을 해주게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 탐색을 빠르게 해줄 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그럼 한개만의 칼럼만 인덱스를 구성할 수 있나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놉. 그렇지는 않다.&lt;br /&gt;예를 들어서 팀에 속한 멤버가, 항상 등번호를 갖고있어야하고 적어도 그 팀에있는 멤버인 홍길동, 1번은 같이 묶여다닌다.&lt;br /&gt;그렇게 된다면 인덱스에 그 둘을 같이 묶어 구성해주는 것이다.&lt;br /&gt;말로 표현한걸 도식화하면 아래와 같다.&lt;br /&gt;| member_id | number | 주소값 |&lt;br /&gt;|-----------|--------|--------------------------|&lt;br /&gt;| 1 | 1 | id가 1이며 등번호도 1인 데이터의 주소값 |&lt;br /&gt;이게 근데 겹칠 수 있는 데이터라면 일반적인 인덱스로 구성시킬 수 있겠지만,&lt;br /&gt;등번호가 유니크한 고유값으로 묶인다면 Unique Index를 구성해줄 수 있다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE INDEX member_index member(name, number);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스로 탐색한 이후는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 이제 우리는 인덱스로 탐색하는 방법을 조금은 안 것 같다.&lt;br /&gt;여기서 인덱스가 유니크가 아닌경우를 좀 더 보려고한다.&lt;br /&gt;일단 유니크 인덱스가 아니라면, 같은 조건으로 묶여있는 데이터가 여러건 있다는 것인데,&lt;br /&gt;이미 인덱스에서 한번 체로 거른 수준처럼 데이터가 걸러졌는데 이후는 full scan을 수행하여 그 데이터들 중 완전한 조건에 부합하는 데이터만 추려 조회하여 결과를 내준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이렇게 되니까 인덱스 참 좋은것 같은데 그럼 칼럼마다 다 생성해주면 좋은거 아닌가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놉. 그렇지 않다.&lt;br /&gt;왜냐면 데이터는 테이블 자체에 저장이 될텐데, 인덱스는 처음에 b-tree 구조이고 정렬을 한다고 했었다.&lt;br /&gt;인덱스를 많이 구성하게 되면, 그 인덱스들이 원하는 조건대로 재정렬을 해주어야 하기 때문에 성능 저하가 발생할 수 있다.&lt;br /&gt;그리고 이 인덱스가 구성되는게 논리적인게 아니고 주소값을 참조하는 값들이 계속 생성하여 디스크에 저장되기 때문에 저장 용량의 한 부분을 차지하게 된다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Where 조건에서 복합 칼럼 인덱스를 안타게 구성할 수도 있는지?&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;당연히 구성해볼 수 있다.&lt;br /&gt;이것은 테이블을 만들고 실제 select 후 실행계획을 분석해보도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-21 오후 10.47.11.png&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAMTEk/btsCoD2nDNS/zvTYQ8Z1ciSkmKWkVZpYzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAMTEk/btsCoD2nDNS/zvTYQ8Z1ciSkmKWkVZpYzK/img.png&quot; data-alt=&quot;demo2 테이블 DDL&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAMTEk/btsCoD2nDNS/zvTYQ8Z1ciSkmKWkVZpYzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAMTEk%2FbtsCoD2nDNS%2FzvTYQ8Z1ciSkmKWkVZpYzK%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;331&quot; height=&quot;224&quot; data-filename=&quot;스크린샷 2023-12-21 오후 10.47.11.png&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;demo2 테이블 DDL&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;자 테이블 구조는 위와같이 DDL을 정의해놓은 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 EXPLAIN 키워드를 사용해서 인덱스를 타는지 안타는지 보게 될 것이다.&lt;br /&gt;일단 기본적으로 member_id와 grade 팀순위를 묶었다.&lt;br /&gt;-&amp;gt; 대충 구성하려고 하다보니 이상한 데이터 구조가 되어버렸다. ㅋㅋㅋㅋㅋㅋㅋ&lt;br /&gt;일단 이 부분은&amp;hellip; 넘어가도록&amp;hellip;ㅎㅎ&lt;br /&gt;각설하고!&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;EXPLAIN SELECT *
FROM test.demo2
WHERE member_id = 1;

EXPLAIN SELECT *
FROM test.demo2
WHERE grade = 1;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 DDL에서 member_id를 먼저 구성하고, 그 뒤에 grade를 넣은 인덱스를 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;member_id를 먼저 구성해주었기 때문에 member_id로 선정렬된 인덱스를 탐색하게 될 것이니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;member_id를 조건에 넣어주면 인덱스를 통해 데이터를 추려주는게 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;member_id를 통한 조회&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-21 오후 10.48.20.png&quot; data-origin-width=&quot;1555&quot; data-origin-height=&quot;57&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cW5KnV/btsCsv24C4x/eNCQKaF6ZMUAOkAf2G2Yy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cW5KnV/btsCsv24C4x/eNCQKaF6ZMUAOkAf2G2Yy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cW5KnV/btsCsv24C4x/eNCQKaF6ZMUAOkAf2G2Yy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcW5KnV%2FbtsCsv24C4x%2FeNCQKaF6ZMUAOkAf2G2Yy0%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;1555&quot; height=&quot;57&quot; data-filename=&quot;스크린샷 2023-12-21 오후 10.48.20.png&quot; data-origin-width=&quot;1555&quot; data-origin-height=&quot;57&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;grade를 통한 조회&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 복합으로 구성된 상태에서는 member_id로 선정렬이 되어있기에 grade만을 where조건에 넣어주면 인덱스 탐색이 불가능하여 보는것처럼 인덱스를 타지 못하게 쿼리가 구성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-21 오후 10.48.09.png&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r74Mx/btsCshKK2Gs/7xlgvX6pGZ7K9rmYrRQBk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r74Mx/btsCshKK2Gs/7xlgvX6pGZ7K9rmYrRQBk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r74Mx/btsCshKK2Gs/7xlgvX6pGZ7K9rmYrRQBk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr74Mx%2FbtsCshKK2Gs%2F7xlgvX6pGZ7K9rmYrRQBk0%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;1548&quot; height=&quot;50&quot; data-filename=&quot;스크린샷 2023-12-21 오후 10.48.09.png&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이렇게 됐을까?&lt;br /&gt;인덱스 구성한 ddl을 보면 member_id 로 시작하게 된다.&lt;/p&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;color: #000000; text-align: center;&quot;&gt;그래서 member_id가 Index에 먼저 구성되어 있기 때문에 우선적으로 추려볼 수는 있는 과정을 거치는 것이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: center;&quot;&gt;그래서 grade만을 조회할 때는 grade로만 정렬이되거나, index의 가장 앞단에 grade로 잡혀있는 둘다 없기 때문에 가장 좋지 않은 Full scan 데이터를 조회하게 되는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;/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;인덱스로 정할 n개의 칼럼들 + 주소 참조값&lt;/b&gt;을 가진 별도의 인덱스를 구성한다고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 해당 조건에 부합하는 row의 모든 데이터가 아니라 인덱스에 포함된 데이터만 조회한다면 사실 테이블 스캔이 필요 없는거 아냐?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;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;h3 data-ke-size=&quot;size23&quot;&gt;그러면 이제 인덱스를 구성하기 좋게 만드려는 조건들을 나열해볼 수 있지 않나?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다. 인덱스를 잘 설계하기 위해서는 어떻게 만들어줘야 하는지 이쯤 되면 조금은 이해가 될 수 있어보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;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;gt; 이래야 조건에 부합하는 데이터의 Full scan을 하더라도 빠르게 찾아낼 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 컬럼 인덱스라면...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 자주 엮이는 칼럼들을 우선적으로 묶어주는데, 그 조합의 유니크함이 필요할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;칼럼의 갯수가 너무 많아지면 반대로 또 인덱스 용량이 무거워지기 때문에 좋지 않을 것이다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 정리 차원에서 인덱스를 정리해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 공부했던 것보다 지금 공부하면서 정리하는게 좀 더 많이 이해할 수 있게 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 조금씩 점진적으로 깊게 공부하는 방법을 천천히 체득시켜야겠다.&lt;/p&gt;</description>
      <category>CS/데이터베이스</category>
      <category>db</category>
      <category>MySQL</category>
      <category>인덱스</category>
      <category>커버링 인덱스</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/138</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%9D%B8%EB%8D%B1%EC%8A%A4#entry138comment</comments>
      <pubDate>Thu, 21 Dec 2023 23:10:54 +0900</pubDate>
    </item>
    <item>
      <title>effectively final 및 lambda capturing에 대해 톺아보기</title>
      <link>https://lsj8367.tistory.com/entry/effectively-final-%EB%B0%8F-lambda-capturing%EC%97%90-%EB%8C%80%ED%95%B4-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게 사용하던 Java의 람다 기능에 대해서 의문을 갖지 않고 막 써댔다.&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;그래서 정리해본다.&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;람다 캡쳐링에 대해서 알아보기 이전에 Effectively final에 대해 먼저 알아보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Effectively Final 이란?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 단어를 deepL을 통해 해석해보면 사실상 최종 이라고 해석해준다.&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;p data-ke-size=&quot;size16&quot;&gt;effectively final이 되려면 아래의 3가지 조건을 만족해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 3가지 조건은 &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.12.4&quot;&gt;공식문서&lt;/a&gt;를 통해서 나와있는 정보들이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;명시적인 final을 선언하지 않았다.&lt;/li&gt;
&lt;li&gt;재 할당을 하지 않아야 한다.&lt;/li&gt;
&lt;li&gt;접두 또는 후미에 증감연산자를 추가해서 데이터를 바꾸지 않아야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체라면 참조 주소값만 바뀌지 않는다면 그대로 계속 effectively final 로 유지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정상적인 Effectively Final&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-08 오전 1.24.42.png&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqjURJ/btsBBMSpsij/HqfCAshaiJWY5Ny6aiApBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqjURJ/btsBBMSpsij/HqfCAshaiJWY5Ny6aiApBK/img.png&quot; data-alt=&quot;정상적인 lambda식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqjURJ/btsBBMSpsij/HqfCAshaiJWY5Ny6aiApBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqjURJ%2FbtsBBMSpsij%2FHqfCAshaiJWY5Ny6aiApBK%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;702&quot; height=&quot;146&quot; data-filename=&quot;스크린샷 2023-12-08 오전 1.24.42.png&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정상적인 lambda식&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Effectively Final이 제대로 되지않은 경우&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-08 오전 1.25.03.png&quot; data-origin-width=&quot;1686&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BRP9h/btsByoLvXvv/51ATYK201pFqfoJGiiJv30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BRP9h/btsByoLvXvv/51ATYK201pFqfoJGiiJv30/img.png&quot; data-alt=&quot;비정상적인 lambda식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BRP9h/btsByoLvXvv/51ATYK201pFqfoJGiiJv30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBRP9h%2FbtsByoLvXvv%2F51ATYK201pFqfoJGiiJv30%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;1686&quot; height=&quot;640&quot; data-filename=&quot;스크린샷 2023-12-08 오전 1.25.03.png&quot; data-origin-width=&quot;1686&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비정상적인 lambda식&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 data-ke-size=&quot;size16&quot;&gt;Lambda Capturing에 대해 알아볼 시간이다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Lambda Capturing이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 정의한 변수를 사용할 때 람다식(익명 클래스의 function)에서 복사본을 생성하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부라는 의미는 지역변수나 전역변수(인스턴스)와 클래스 변수들을 전부 아우르는 표현이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그럼 Capturing을 적용하지 않는 경우도 있을거 아닌가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 사용하지 않을 수 있다. 그래서 변수를 넣지 않고 동작할 수 있는데 이때는 외부의 변수를 주입받아 사용하는게 아니라서 캡쳐링이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용되지 않아서 non-capturing이라고 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Lambda Capturing은 왜 복사본을 만드는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 지역변수는 메모리 구조상 스택 영역에 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택은 스레드가 실행됐을 때 고유한 영역으로 가지고 있게된다. 그래서 스레드끼리는 공유할 수 없고, 스레드가 종료되면 해당 스택 영역도 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 이제 문제가 발생하는 것이다.&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;아래 코드에서 ss 라는 문자열을 복사해서 갖고있지 않는다면 new Thread 부분에선 지역변수로 묶여있는 test()가 스레드보다 더 빨리 수행되고 끝날 가능성이 존재하기 때문에 null을 줄 수도 있을 것이다. 이렇기 때문에 복사본을 만들어 유지하는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1701968209050&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void test() {
    String ss = &quot;test&quot;;

    new Thread(() -&amp;gt; {
        try {
            Thread.sleep(1000L);
            System.out.println(&quot;thread1 ss : &quot; + ss);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }).start();

    System.out.println(&quot;ss : &quot; + ss);
}&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;여기서 근데 effectively final이어야 하는 이유는 위에서 봤듯이 멀티 스레드 환경에서 람다식이 동작할 수 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역변수는 또 스레드마다 공유하지 않기도 한다. 때문에 어떤 복사본이 최신인지를 자바 입장에서는 확인할 방법이 없기 때문에 final변수로만 지역변수를 사용해야 하는것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조용하던 인스턴스 변수나 클래스 변수는?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 변수는 메모리 구조상 힙에 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 알고있다. 힙은? -&amp;gt; 모든 스레드가 공유할 수 있는 메모리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스변수는 static 변수가 이 부분에 포함되는데, 메소드 영역에 할당이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 값이 바뀌던 말던 그 데이터는 항상 같게끔 유지할 수 있기 때문에 할당할 수 있는 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다식 내부에서 지역변수를 사용하는 경우 final이나 effectively final 변수를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 이유는 메모리 구조의 stack 영역에 저장되기 때문&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;</description>
      <category>Java</category>
      <category>Java</category>
      <category>Lambda</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/137</guid>
      <comments>https://lsj8367.tistory.com/entry/effectively-final-%EB%B0%8F-lambda-capturing%EC%97%90-%EB%8C%80%ED%95%B4-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0#entry137comment</comments>
      <pubDate>Fri, 8 Dec 2023 02:05:42 +0900</pubDate>
    </item>
    <item>
      <title>Kafka가 내 로직을 9번이나 재시도를 했다</title>
      <link>https://lsj8367.tistory.com/entry/Kafka%EA%B0%80-%EB%82%B4-%EB%A1%9C%EC%A7%81%EC%9D%84-9%EB%B2%88%EC%9D%B4%EB%82%98-%EC%9E%AC%EC%8B%9C%EB%8F%84%EB%A5%BC-%ED%96%88%EB%8B%A4</link>
      <description>&lt;h1&gt;개요&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 기술스택에서 카프카를 사용하고 있다.&lt;br /&gt;내가 이번에 정리하는 글은 막연하게 ack을 날리는 부분에 있어서 간과했던 로직 때문에 벌어진 이슈를 정리한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떻게 로직이 생겼었는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 카프카를 사용하기 위해선 스프링 부트에서 &lt;code&gt;Spring for Apache Kafka&lt;/code&gt; 를 사용해야 한다.&lt;br /&gt;나는 로직에서 &lt;code&gt;try-catch-finally&lt;/code&gt; 를 붙여 사용했는데 동료 개발자분께서 의도하신 것인지는 모르겠으나 catch절을 빼고 로직을 구성했다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@KafkaListener
public void consumeExample(final ScrapeUpdateDto dto, final Acknowledgement ack) {
    try {
        // do something
    } finally {
        ack.acknowledge();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식으로 구현을 했었고, try 내부에서는 throw를 하는 부분이 군데군데 적용이 되어있었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에러는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플 코드에서 do something에 해당하는 로직에서 문제가 발생하게 되는데, 그것이 바로 재시도 로직은 어디에도 존재하지 않았다.&lt;br /&gt;하물며 Spring측에서 제공하는 &lt;code&gt;@Retryable&lt;/code&gt; 이나 Spring Cloud Openfeign에서 설정하는 &lt;code&gt;Retry&lt;/code&gt; 를 어디에도 구성하지 않았는데 실제 호출은 원본 호출까지 합쳐 10번을 수행했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;디버깅 시 당연하다고 생각한 부분&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 디버깅을 진행했을 때 아 그럼 예외 핸들러가 뭐 처리를 했겠지 라는 생각으로 대충 ErrorHandler를 검색했다.&lt;br /&gt;검색하고 보니까 뭔가 힌트는 얻었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것이 바로 &lt;code&gt;DefaultErrorHandler&lt;/code&gt;&lt;br /&gt;근데 이 클래스가 10회가 발생하고 백오프 9번 재시도 한다고 하는데 이 부분을 생성해주는 곳은 어디에도 없었으며, 변수로 명시한 setter 메소드에도 디버깅 포인트를 집어놓아도 null만을 주입하고 있었다.&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;1302&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUfOSL/btszCoeGUxo/eGCIMzcQtAawu2yyK3PLlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUfOSL/btszCoeGUxo/eGCIMzcQtAawu2yyK3PLlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUfOSL/btszCoeGUxo/eGCIMzcQtAawu2yyK3PLlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUfOSL%2FbtszCoeGUxo%2FeGCIMzcQtAawu2yyK3PLlK%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;1302&quot; height=&quot;208&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 잘 모르겠으니 차례대로 디버깅을 수행해보도록 하자!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디버깅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 &lt;code&gt;KafkaAnnotationDrivenConfiguration&lt;/code&gt; 을 통해 &lt;code&gt;ConcurrentKafkaListenerContainerFactoryConfigurer&lt;/code&gt; 를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 값이 default 설정이라 그런가 전부 errorHandler 부분이 null을 가지고 있다.&lt;br /&gt;여기서 DefaultErrorHandler는 절대 사용되지 않는다라고 확정지었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co730e/btszA4nw2fv/0ttRRWc1Js3t6kwHRktow1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co730e/btszA4nw2fv/0ttRRWc1Js3t6kwHRktow1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co730e/btszA4nw2fv/0ttRRWc1Js3t6kwHRktow1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco730e%2FbtszA4nw2fv%2F0ttRRWc1Js3t6kwHRktow1%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;1774&quot; height=&quot;650&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 &lt;code&gt;ConcurrentKafkaListenerContainerFactory&lt;/code&gt;를 커스텀한 설정을 넣지 않는다면 자동생성 빈으로 구성해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2552&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VM6fQ/btszDUxalk6/0qZtNUAyJQzhLMk7tYhZG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VM6fQ/btszDUxalk6/0qZtNUAyJQzhLMk7tYhZG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VM6fQ/btszDUxalk6/0qZtNUAyJQzhLMk7tYhZG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVM6fQ%2FbtszDUxalk6%2F0qZtNUAyJQzhLMk7tYhZG0%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;2552&quot; height=&quot;364&quot; data-origin-width=&quot;2552&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;죄다 이런 default 클래스들로 구성을 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 기본 팩토리 구성을 끝마친다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;팩토리 생성 이후&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팩토리가 만들어졌다면 당연히 Listener들을 생성시켜주어야 하는데, 이 부분을 &lt;code&gt;createListenerContainer&lt;/code&gt; 메소드로 생성해주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 로직은 &lt;code&gt;ConcurrentKafkaListenerContainerFactory&lt;/code&gt; 가 상속한 &lt;code&gt;AbstractKafkaListenerContainerFactory&lt;/code&gt; 에 구성된 로직을 오버라이딩한 메소드이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AmmYP/btszyRJgnZB/zuXwj3P8iK9B6M9WgTjM1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AmmYP/btszyRJgnZB/zuXwj3P8iK9B6M9WgTjM1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AmmYP/btszyRJgnZB/zuXwj3P8iK9B6M9WgTjM1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAmmYP%2FbtszyRJgnZB%2FzuXwj3P8iK9B6M9WgTjM1k%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;1146&quot; height=&quot;480&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;createListenerContainerInstance&lt;/code&gt; 로 리스너에 대한 부분을 생성해주게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 추가적으로 알게된 것은 카프카를 설정하기 위해 yaml파일에 구성하는 concurrency 값을 토대로 리스너 컨테이너를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZrfZS/btszDzUi8b6/b4ehl7LT3NuALWibe0PLbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZrfZS/btszDzUi8b6/b4ehl7LT3NuALWibe0PLbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZrfZS/btszDzUi8b6/b4ehl7LT3NuALWibe0PLbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZrfZS%2FbtszDzUi8b6%2Fb4ehl7LT3NuALWibe0PLbK%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;1458&quot; height=&quot;328&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어 잠깐만! 근데 리스너 컨테이너 생성하면 리스너가 여기서 설정되는거 아냐?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 리스너 컨테이너도 마찬가지로 추상 컨테이너를 상속받고 있는데, 이 부분을 살펴봤다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 리스너면 컨슈머에 대한 설정 부분이 어느정도 구현이 되어있을 것이라고 추측하고 들어가보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수에는 롤백 프로세서 변수가 default 설정이 되어있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 AbstractMessageListenerContainer의 변수로 선언된 일부를 가져왔다.&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;private AfterRollbackProcessor&amp;lt;? super K, ? super V&amp;gt; afterRollbackProcessor = new DefaultAfterRollbackProcessor&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DefaultAfterRollbackProcessor를 따라가보면 정답이 나올 것 같아서 바로 들어갔다~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 설정 구성이 들어가있었고, &lt;code&gt;DefaultErrorHandler&lt;/code&gt;와 같이 최초 1회 수행한 후 로직내부의 별도의 Exception을 처리해주는 부분이 없다면 9회를 재시도하도록 구성이 되어있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;1136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VfU36/btszzWJ0SYu/OegjkQBKMIKCWoftUMHOUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VfU36/btszzWJ0SYu/OegjkQBKMIKCWoftUMHOUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VfU36/btszzWJ0SYu/OegjkQBKMIKCWoftUMHOUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVfU36%2FbtszzWJ0SYu%2FOegjkQBKMIKCWoftUMHOUk%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;1570&quot; height=&quot;1136&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;1136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;디버깅 끝낸 후&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 끝내고 난 뒤 공식문서를 봤는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에서 deafult error handler 설정 값이 어떻게 들어가있는지 알려주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-kafka/docs/current/reference/html/#commonErrorHandler&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서 바로가기&lt;/a&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 코드에서 설명해주고 있는 부분은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AbstractKafkaListenerContainerFactory내부에 있는데,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deprecated 처리된 메소드들&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;2.2버전 이상
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;setErrorHandler&lt;/li&gt;
&lt;li&gt;setBatchHandler&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;2.8버전 이상
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;setRetryTemplate&lt;/li&gt;
&lt;li&gt;setRecoveryCallback&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전부 &lt;code&gt;setCommonErrorHandler&lt;/code&gt; 와 &lt;code&gt;setAfterRollbackProcessor&lt;/code&gt; 메소드를 사용하도록 추천하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1560&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhzrhq/btszy5m8TGs/lFACnfSukEyvDHE8sSHMC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhzrhq/btszy5m8TGs/lFACnfSukEyvDHE8sSHMC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhzrhq/btszy5m8TGs/lFACnfSukEyvDHE8sSHMC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhzrhq%2Fbtszy5m8TGs%2FlFACnfSukEyvDHE8sSHMC1%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;1560&quot; height=&quot;724&quot; data-origin-width=&quot;1560&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;결론&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 무조건적으로 ack을 날리더라도 dead letter를 바로바로 처리해줄 수 있는 또 하나의 프로듀서, 컨슈머 한쌍을 만들어 두던지, 아니면 db에 따로 오류난 메시지들을 적재하고 배치로 수행해주던지 구성이 필요할 것 같고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러를 명확하게 던진 부분을 컨슘하는 곳에서 명확하게 처리해줄 상황이 아니라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Custom한 ErrorHandler를 구성하는게 바람직할 것 같다.&lt;/p&gt;</description>
      <category>디버깅</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/136</guid>
      <comments>https://lsj8367.tistory.com/entry/Kafka%EA%B0%80-%EB%82%B4-%EB%A1%9C%EC%A7%81%EC%9D%84-9%EB%B2%88%EC%9D%B4%EB%82%98-%EC%9E%AC%EC%8B%9C%EB%8F%84%EB%A5%BC-%ED%96%88%EB%8B%A4#entry136comment</comments>
      <pubDate>Thu, 2 Nov 2023 01:11:18 +0900</pubDate>
    </item>
    <item>
      <title>Slack Slash Commands(슬랙 슬래시 커맨드) 사용하기</title>
      <link>https://lsj8367.tistory.com/entry/%EC%8A%AC%EB%9E%99-%EC%8A%AC%EB%9E%98%EC%8B%9C-%EC%BB%A4%EB%A7%A8%EB%93%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 어떻게 하면 좀 더 편하게 어떤 기능을 사용하게 할 수 있을까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 생각이 계속해서 들었는데, 좋은 기회가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 영업을 하시는분들이 컨퍼런스, 세미나 등등을 많이 참석하여 홍보하고 고객을 유치하는 과정에서 기회가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것은 회사의 카드를 발급을 받았는지에 대한 여부 확인을 따로 할 수 없었다는 것인데, 당연히 그럴 것이 회원가입할 때 법인 유무를 체크하지는 않으니 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 종이로든 아니면 엑셀시트던 특정 자료를 세미나 전에 준비해서 가는 부분이 매우 불편해보였다. (갈 때마다 추가 법인이 있으니까 매번 정리해야 된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자! 그래서 슬랙에서 &lt;code&gt;/법인찾기 법인명&lt;/code&gt; 하면 우리에게 가입된 법인인지를 찾는 기능을 구현하고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 데모로 슬래시 커맨드를 만드는 법을 데모로 만드는 과정을 포스팅한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 다는 얘기지만 모든 코드는 &lt;a title=&quot;깃허브&quot; href=&quot;https://github.com/lsj8367/laboratory/tree/master/slack&quot;&gt;깃허브&lt;/a&gt; 에 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;슬랙 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack Slash Commands&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.slack.com/apps&quot;&gt;https://api.slack.com/apps&lt;/a&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;br /&gt;나는 봇을 만드는 과정을 진행하는 포스트는 아니기에 다른 포스트에서 알아보도록 합시다.&lt;/li&gt;
&lt;li&gt;Basic Information Signing Secret&lt;br /&gt;Settings &amp;gt; Basic Information &amp;gt; App Credentials 탭의 &lt;code&gt;Signing Secret&lt;/code&gt; 정보를 따로 저장한다.&lt;/li&gt;
&lt;li&gt;Features &amp;gt; OAuth &amp;amp; Permissions &amp;gt; OAuth Tokens for Your Workspace 탭의 &lt;code&gt;Bot User OAuth Token&lt;/code&gt; 정보를 따로 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 저장하면 모든 설정은 준비됐다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;애플리케이션을 만들자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 코드는 회사에선 자바를 사용하지만, 집에서는 공부 목적 + 나중 을 위해 코틀린을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack은 SDK를 사용할 수 있도록 자체 라이브러리를 제공해주는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 bolt를 사용하여 구현할 계획이다. 자세한 정보는 &lt;a href=&quot;https://slack.dev/java-slack-sdk/guides/slash-commands&quot;&gt;https://slack.dev/java-slack-sdk/guides/slash-commands&lt;/a&gt; 에 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;build.gradle&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성에는 위에서 말한것과 같이 bolt에 대한 설정을 추가해준다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;dependencies {
    implementation(&quot;com.slack.api:bolt:1.29.2&quot;)
    implementation(&quot;com.slack.api:bolt-servlet:1.29.2&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SlackConfiguration&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬래시 커맨드에 대한 수신 정보들을 해당 클래스에서 미리 설정한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
class SlackConfiguration {
    @Bean
    fun testApp(): App {
        // 슬랙 앱 설정
        val app = App(
            AppConfig.builder()
                     .signingSecret(&quot;&quot;) //위에서 설명했던 Signing Secret 정보 넣기
                        .singleTeamBotToken(&quot;&quot;) // 위에서 설명했던 OAuth Token 정보 넣기
                     .build()
        )

        app.command(&quot;/echo&quot;, (req, ctx) -&amp;gt; {
              String commandArgText = req.getPayload().getText(); // text에 대한 값을 가져온다.
            String channelId = req.getPayload().getChannelId(); // 채널 id 수신
            String channelName = req.getPayload().getChannelName(); //채널명 수신
            String text = &quot;테스트 발송&quot;;
            return ctx.ack(text); //슬랙으로 원하는 정보를 전송
        });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bean으로 각 호출할 커맨드들을 앱을 통해 등록해준다.&lt;br /&gt;여기서 좀 더 고도화 하자면 AppConfig는 따로 분리하여 전역적으로 사용해도 무방하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SlackSender&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 bolt에서 추가해준 라이브러리를 통해 SlackServlet을 생성할 것이다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@WebServlet(&quot;/slack/events/test&quot;)
class SlashCommand(@Qualifier(&quot;testApp&quot;) app: App?) : SlackAppServlet(app)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현은 끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되고 나서는 슬랙에서 추가적으로 설정해주어야 하는 부분이 존재한다.&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;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mS5l7/btsmGANbKpR/Hb0fyjEZgfERJWU2guDoI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mS5l7/btsmGANbKpR/Hb0fyjEZgfERJWU2guDoI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mS5l7/btsmGANbKpR/Hb0fyjEZgfERJWU2guDoI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmS5l7%2FbtsmGANbKpR%2FHb0fyjEZgfERJWU2guDoI1%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;440&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;440&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;localhost를 사용한다면 내가 구현한 것처럼 ngrok등의 터널링 도구를 사용해서 서버를 라우팅해주어야 한다.&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;548&quot; data-origin-height=&quot;781&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzdAfm/btsmGQPLndL/aJQ8ixIs4KNd54LWw9dnpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzdAfm/btsmGQPLndL/aJQ8ixIs4KNd54LWw9dnpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzdAfm/btsmGQPLndL/aJQ8ixIs4KNd54LWw9dnpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzdAfm%2FbtsmGQPLndL%2FaJQ8ixIs4KNd54LWw9dnpK%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;548&quot; height=&quot;781&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;781&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;requestURL에는 ngrok 주소 + &lt;code&gt;/slack/events&lt;/code&gt; 를 붙여주고 맨 마지막은 command 경로를 넣어준다.&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;code&gt;/command&lt;/code&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;354&quot; data-origin-height=&quot;69&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KSujj/btsmHirBSvP/3gB99w8mRjFxFuuBkLy0rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KSujj/btsmHirBSvP/3gB99w8mRjFxFuuBkLy0rK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KSujj/btsmHirBSvP/3gB99w8mRjFxFuuBkLy0rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKSujj%2FbtsmHirBSvP%2F3gB99w8mRjFxFuuBkLy0rK%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;354&quot; height=&quot;69&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;69&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 안되는 것인가...?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@ServletComponentScan&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 슬랙 웹 서블릿이라는 클래스를 추가해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 클래스는 내부에서 &lt;code&gt;HttpServlet&lt;/code&gt; 을 상속받아 구현하고 있기에 servlet을 수신할 수 있는 설정을 구현해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문단의 제목인 &lt;code&gt;@ServletComponentScan&lt;/code&gt; 을 통해 서블릿 객체들을 스캔해줄 수 있도록 설정을 해주어야 한다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;@ServletComponentScan(basePackages = [&quot;com.github.lsj8367.slack.command&quot;])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 Application 클래스 위에 설정을 해주거나, 또는 별도의 설정 클래스를 통해 지정해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정해주고 다시 실행해주게 되면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드랑 조금은 상이하게 테스트라는 단어 3번을 출력하도록 해주었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;51&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5VKKn/btsmHGZ2U81/kfxct2gUyn7RUxORhHcJ6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5VKKn/btsmHGZ2U81/kfxct2gUyn7RUxORhHcJ6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5VKKn/btsmHGZ2U81/kfxct2gUyn7RUxORhHcJ6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5VKKn%2FbtsmHGZ2U81%2Fkfxct2gUyn7RUxORhHcJ6k%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;51&quot; height=&quot;68&quot; data-origin-width=&quot;51&quot; data-origin-height=&quot;68&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;그리고 기본적으로는 슬랙의 문법을 다 알아듣기 때문에 &lt;a href=&quot;https://api.slack.com/reference/surfaces/formatting&quot;&gt;https://api.slack.com/reference/surfaces/formatting&lt;/a&gt; 여기서 사용할만한 문법을 적용해서 추가해주면 되겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 이제 회사에 적용하면? &lt;code&gt;불편했던 점을 자동화한 개발자&lt;/code&gt; 타이틀에 1%는 달성하지 않았나? 싶다~  &lt;/p&gt;</description>
      <category>Spring</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/135</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%8A%AC%EB%9E%99-%EC%8A%AC%EB%9E%98%EC%8B%9C-%EC%BB%A4%EB%A7%A8%EB%93%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0#entry135comment</comments>
      <pubDate>Thu, 6 Jul 2023 23:07:21 +0900</pubDate>
    </item>
    <item>
      <title>Stream 오류 제거</title>
      <link>https://lsj8367.tistory.com/entry/Stream-%EC%98%A4%EB%A5%98-%EC%A0%9C%EA%B1%B0</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;의문점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크래핑 로직 실행 시간에 대해 의문을 가졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균 시간이 그럭저럭 다들 비슷한 수준에 머무는데, 이상하게 한 부분만 너무 느렸다. 증권사에 대한 스크래핑 내역이었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 증권사만 중복 로그인이 감지되었을 경우 30초를 대기했다가 다시 수행할 수 있게 하는 로직이 들어있었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드는 그에맞는 정말 30초를 기다리는지에 대한 여부를 테스트 하고 있었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  그냥 30초를 기다리는것보단 특정한 법인이 아니라면 30초를 기다리지 않는다 를 테스트하면 되지 않을까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 생각했던 이유는 30초를 테스트에서 기다리는 비용이 비싸다고 생각했고, 전체 테스트를 돌릴 때 이 부분때문에 30초를 더 기다려야 한다는 것이다. 그리고 milliseconds 차이값을 비교했기에 간헐적으로 실패하는 경우도 있었다.&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;디버깅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 디버깅 시작했는데 내가 stream 로직을 잘 구성하지 못하는 것인가? 라는 생각도 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stream 내부에 stream을 또 구성하고 있는 상황이었다.&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;이말은, stream 내부의 stream에선 어떤 값을 반환하던 간에 바깥에서 Stream으로 래핑을 하고 있으니 뭘해도 값이 존재한다는 가정이 먹힌다는 뜻이다.&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;p data-ke-size=&quot;size16&quot;&gt;가장 짤막하게 예시를 들어볼 수 있는게 햄버거가 아닐까 싶었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안에 토핑을 생각하다가 간단하게 햄버거 이름, 빵종류, 소스가 무엇인지만 적어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 햄버거 재료중에 패티가 빠지면 섭한데 대충 보자. ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 14버전부터 해당 record 클래스가 등장했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 집에서는 java 17버전을 쓰고있기에 record로 적었고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지와 아래의 코드는 같은것이라고 생각하면 되겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면서 getter가 기본으로 구현되어있고 getXXX() 방식이 아닌 바로 변수 이름과 같은 메소드를 호출할 수있다.&lt;/p&gt;
&lt;pre id=&quot;code_1682001636178&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class Hamburger {
    private final String name;
    private final String bread;
    private final List&amp;lt;Sauce&amp;gt; sauces;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uqaRT/btsbBZb2vyd/GnmHRDtahMnrl16Gllw87k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uqaRT/btsbBZb2vyd/GnmHRDtahMnrl16Gllw87k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uqaRT/btsbBZb2vyd/GnmHRDtahMnrl16Gllw87k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuqaRT%2FbtsbBZb2vyd%2FGnmHRDtahMnrl16Gllw87k%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;290&quot; height=&quot;294&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;294&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;아무튼 이런 햄버거와 안에 들어간 소스를 봐야하는데 소스도 정말 대충 value라고 이름을 지었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;198&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cizukj/btsbBPUOsmj/diJOmZvKtV0l1AtqWfJgEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cizukj/btsbBPUOsmj/diJOmZvKtV0l1AtqWfJgEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cizukj/btsbBPUOsmj/diJOmZvKtV0l1AtqWfJgEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcizukj%2FbtsbBPUOsmj%2FdiJOmZvKtV0l1AtqWfJgEK%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;198&quot; height=&quot;132&quot; data-origin-width=&quot;198&quot; data-origin-height=&quot;132&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;이런 코드를 예시로 만들었다. 로직만 다를뿐이지 이슈가 있던 로직과 같은 구현법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;햄버거들에서 소스만 발라내서 매운 소스가 들어간지 여부를 확인하여 있으면 true 없으면 false를 반환하려고 했었던 로직인 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4X85p/btsbBbKDdEk/8gEVTmLGJvji23IvWphhN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4X85p/btsbBbKDdEk/8gEVTmLGJvji23IvWphhN0/img.png&quot; data-alt=&quot;문제가 있던 로직&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4X85p/btsbBbKDdEk/8gEVTmLGJvji23IvWphhN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4X85p%2FbtsbBbKDdEk%2F8gEVTmLGJvji23IvWphhN0%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;632&quot; height=&quot;265&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;265&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;잘보면 뭐 햄버거들중에 소스를 발라내고 그 소스들을 뒤져가면서 null이 아니며 spicy 소스인 데이터를 찾으면 바로 true 반환하려고 했던 것 같다.&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;위에서 설명했던 30초 로직은 해당 메소드에서 true가 반환되면 타게 되어있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public으로 구현했지만 회사의 로직은 private으로 구현되어 있는 메소드였다.&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;각설하고, 이부분에 대해 테스트를 해보니 &quot;spicy&quot; 라는 값을 넣지 않아도 무조건 테스트가 항상 true였다.&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;소스에 살사와 머스타드를 넣었음에도 불구하고 spicy 소스가 없는데 참이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1DjdB/btsbA4Sho9j/ywo9jkJy3mRvUQtgJ9xeiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1DjdB/btsbA4Sho9j/ywo9jkJy3mRvUQtgJ9xeiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1DjdB/btsbA4Sho9j/ywo9jkJy3mRvUQtgJ9xeiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1DjdB%2FbtsbA4Sho9j%2Fywo9jkJy3mRvUQtgJ9xeiK%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;1558&quot; height=&quot;614&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;614&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stream내부에 stream이 있지 않는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부에서 뭘 수행하던간에 아직 stream 연산을 끝맺어주지 않았기 때문에 Stream&amp;lt;&amp;gt; 객체를 반환해줄 것이고 그렇기 때문에 항상 그 내부 연산이 존재하던 않던 상관없이 Stream 객체 자체가 null이 아니고 존재하기 때문에 findFirst() 를 수행해도 항상 참을 반환하는 것이었다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어떻게 이 부분을 개선했는가?&lt;/p&gt;
&lt;pre id=&quot;code_1682002831269&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public boolean isExistSpicySauce(List&amp;lt;Hamburger&amp;gt; hamburgers) {

        final Set&amp;lt;Sauce&amp;gt; sauces = hamburgers.stream()
            .map(hamburger -&amp;gt; hamburger.sauces())
            .flatMap(List::stream)
            .collect(Collectors.toSet());

        return sauces
            .stream()
            .filter(sauce -&amp;gt; sauce.value() != null)
            .anyMatch(sauce -&amp;gt; &quot;spicy&quot;.equals(sauce.value()));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 개선했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 당연히 spicy가 포함되지 않았기 때문에 false를 반환했고 테스트도 성공한것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IbSpE/btsbzKzYJAr/pYKONgwWBmV91GFN7Sn6V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IbSpE/btsbzKzYJAr/pYKONgwWBmV91GFN7Sn6V0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IbSpE/btsbzKzYJAr/pYKONgwWBmV91GFN7Sn6V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIbSpE%2FbtsbzKzYJAr%2FpYKONgwWBmV91GFN7Sn6V0%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;1554&quot; height=&quot;652&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stream으로 엮어주고 Set으로 변환한 이유는 어떤 햄버거든 소스들만 추려 spicy만 있으면 됐기 때문에 이런식으로 구현을 해주었다.&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;stream내부에서 stream을 또 사용할 때에 주의해서 써야겠다고 이 부분을 수정하면서 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쩌면 너무 당연한 얘기일 수 있었지만, 이 부분 때문에 모든 스크래핑에서 30초 정도의 중복 로그인 대기 시간이 해소되었고 30초 * n개의 증권사 스크래핑 시간을 특정 기관에서만 중복로그인 30초만 대기할 수 있도록 수정되었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;무의미한 스크래핑 대기 시간을 해소했다는 얘기이다!!&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 한동안 너무 신규 기능개발건에 대해 초점이 맞춰져서 힘들었었는데, 계속해서 기존 코드 부수고 고치며 성능개선하며 이전 코드들을 돌아볼 수 있는 시간을 생각을 전환하니 어느새 갖고있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 생각의 전환은 &lt;a href=&quot;https://jojoldu.tistory.com/710&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jojoldu.tistory.com/710&lt;/a&gt; 여기서 자극을 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;많이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;받게 되었다.&lt;/p&gt;</description>
      <category>디버깅</category>
      <category>Java</category>
      <category>STREAM</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/134</guid>
      <comments>https://lsj8367.tistory.com/entry/Stream-%EC%98%A4%EB%A5%98-%EC%A0%9C%EA%B1%B0#entry134comment</comments>
      <pubDate>Fri, 21 Apr 2023 00:07:45 +0900</pubDate>
    </item>
    <item>
      <title>분산 락</title>
      <link>https://lsj8367.tistory.com/entry/%EB%B6%84%EC%82%B0-%EB%9D%BD</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;분산 락&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 스크래핑 작업을 수행하다가 분산락을 구현하게 되어 너무 잦은 야근으로 인해 이제야 포스팅을 끄적여본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번의 스크래핑요청은 곧바로 비용 + 시간이 요구된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용이 요청때마다 들고, 내가 구현했던 정부기관의 스크래핑 시간은 길면 15초까지 걸리는 작업도 있다. (의존도가 너무 높은것도 사실이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 비용과 시간이 드는 스크래핑 작업을 동시 다발적으로 같은 작업을 수행해준다?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것도 문제가 있을 것이다. 한번의 스크래핑 데이터를 계속해서 돌려쓰면 되지않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 스크래핑 데이터를 공유자원이라 생각했고 이미 요청중인 작업이 있다면 이미 작업중인 스크래핑이 있다고 오류를 내려주는게 오히려 적합하다고 생각했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Synchronized&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Java에서 제공해주는 synchronized 키워드를 사용하여 동시성을 제어하는 것은 어떨까 하고 생각해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 내부의 monitor를 기반으로 상호 배제 기능을 제공해준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Monitor란?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.notion.so/3-JVM-Synchronization-3aeccc3c725a4ecaa01d6f9cbb16b3f2&quot;&gt;3. JVM Synchronization이란?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당부분에서 책 스터디를 하며 monitor에 대해 정리했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지만 보면 오! 동시성 제어 완벽하게 할 수 있겠지만, monitor는 1개니까 비용이 비쌀것으로 예상해 적재적소에 꼭 사용해야 하는 곳만 사용해야겠다! 라고 처음 생각했다.&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;b&gt;정상&lt;/b&gt; 개발환경 - 이 환경은 서버 1개로만 운영되어도 문제없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제&lt;/b&gt; 운영환경 - 이 환경은 이중화 구성이 되어있고 부하분산 되어있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분산락&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 등장한게 바로 이 분산락이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis를 사용하여 구현을 했는데 락이라는 단어가 나왔다고 해서 DB에서의 락을 생각하면 다른 개념이므로 주의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB의 락을 이용하여 동시성 제어를 할 수는 있지만, 분산락으로 풀어내는게 더 많을 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그럼 왜?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐면 일단 DB의 락을 건다는 것 자체가 결국 DB에 대한 I/O가 한번은 일어나야 락상태인지 어떤지 알 수 있기 때문에 DB 조회 1회를 무조건 1번은 한다는 전제를 깔고 간다 생각해서 동시성을 컨트롤 해주려면 그 앞단에서부터 끊어줘야 될 것이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 대표적으로 많이 쓰는 Redis를 사용하여 분산락을 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이 개념은 키 하나를 갖고 있어서 이 키가 있다면 락이 잡혀있다고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이중화 구성이 된 서버들이 하나의 Redis를 바라보며 임계영역에 대해 접근을 할 수 있는지를 체크한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이득은?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게되면 맨 위에서 언급했던 비용 + 시간을 절약할 수 있는 구성이 되었다.&lt;/p&gt;
&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;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 말하지만 모든 코드는 나의 &lt;a href=&quot;http://github.com/lsj8367&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&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;scrapeSomething 메소드를 실행하는 경우 synchronized옵션을 주어 설정한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 프로세스마다의 실행이 1회씩 수행될 것이다.&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-filename=&quot;스크린샷 2023-04-15 오전 2.46.49.png&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;882&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uYDh5/btsak9lGfXJ/erKzmkRTd64MFDpCjC5HMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uYDh5/btsak9lGfXJ/erKzmkRTd64MFDpCjC5HMk/img.png&quot; data-alt=&quot;코틀린으로 구현해본 분산락 로직&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uYDh5/btsak9lGfXJ/erKzmkRTd64MFDpCjC5HMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuYDh5%2Fbtsak9lGfXJ%2FerKzmkRTd64MFDpCjC5HMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;945&quot; height=&quot;882&quot; data-filename=&quot;스크린샷 2023-04-15 오전 2.46.49.png&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;882&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코틀린으로 구현해본 분산락 로직&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이 분산락을 통해 새로고침하고 다시 스크래핑 수행하는 요청을 막아서 비용이 비싼 스크래핑 로직을 안태울 수 있게 로직을 변경했다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/133</guid>
      <comments>https://lsj8367.tistory.com/entry/%EB%B6%84%EC%82%B0-%EB%9D%BD#entry133comment</comments>
      <pubDate>Sat, 15 Apr 2023 02:50:24 +0900</pubDate>
    </item>
    <item>
      <title>@Transactional 제대로 알고쓰기</title>
      <link>https://lsj8367.tistory.com/entry/Transactional-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B3%A0%EC%93%B0%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;예제는 &lt;a href=&quot;https://github.com/lsj8367/laboratory/commit/59d95d494af764a47ce46dc802a532427084c7a1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&gt;에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션을 공부했다고 생각하고 업무에 임했던 나였는데, 도저히 풀리지않는 느낌으로 예외를 받았던게 있다.&lt;br /&gt;이전에 한번 포스팅했던 &lt;code&gt;Stream Closed&lt;/code&gt; 에러였는데 이게 또 한번 나를 붙잡았다.&lt;br /&gt;처음 개발환경에서 OpenFeign을 사용하면서 또 logging level에서 IOException이 나는줄알고 이부분으로 삽질을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 그게 아니어서 이 포스팅을 작성했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HelloController.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
@RequiredArgsConstructor
public class TestController {

    private final TransactionParentService transactionParentService;

    @GetMapping(&quot;/transaction/test&quot;)
    public ResponseEntity&amp;lt;?&amp;gt; test() {
        transactionParentService.size();
        return ResponseEntity.ok().build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TransactionParentService.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class TransactionParentService {

    private final MemberService memberService;
    private final TransactionErrorService transactionErrorService;

    @Transactional
    public int size() {
        transactionErrorService.throwExceptionLog();
        return memberService.allSize();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TransactionErrorService.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Slf4j
@Service
@RequiredArgsConstructor
public class TransactionErrorService {

    private final MemberService memberService;

    @Transactional
    public void throwExceptionLog() {
        try {
            memberService.saveAndException();
        } catch (RuntimeException e) {
            log.error(&quot;error : {}&quot;, e.getMessage());
        }
        System.out.println(&quot;끝&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MemberService.java&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;

    public void save() {
        memberRepository.save(new Member(1L, &quot;홍길동&quot;));
    }

    @Transactional
    public void saveAndException() {
        memberRepository.save(new Member(null, &quot;홍길동&quot;));
        throw new RuntimeException();
    }

    public int allSize() {
        return memberRepository.findAll().size();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TransactionParentService -&amp;gt; TransactionErrorService -&amp;gt; MemberService 순서로 서비스는 동작하게 되고,&lt;br /&gt;기본적으로 Service에는 메소드마다 &lt;code&gt;@Transactional&lt;/code&gt; 어노테이션이 붙어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 application.yaml에&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;logging:
  level:
    org.springframework.transaction: trace&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 로그설정을 해주고 찍어본 결과이다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Getting transaction for [com.github.lsj8367.service.TransactionParentService.size]
TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Getting transaction for [com.github.lsj8367.service.TransactionErrorService.throwExceptionLog]
TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Getting transaction for [com.github.lsj8367.service.MemberService.saveAndException]
TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]

TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Completing transaction for [com.github.lsj8367.service.MemberService.saveAndException] after exception: java.lang.RuntimeException

TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Completing transaction for [com.github.lsj8367.service.TransactionErrorService.throwExceptionLog]
TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Getting transaction for [com.github.lsj8367.service.MemberService.allSize]
TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]

TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Completing transaction for [com.github.lsj8367.service.MemberService.allSize]
TRACE 66119 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor           : Completing transaction for [com.github.lsj8367.service.TransactionParentService.size]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 보면 정상적으로 트랜잭션을 처음엔 쭉 획득한다.&lt;br /&gt;그리고나서 &lt;code&gt;memberService.save()&lt;/code&gt;를 통해 &lt;code&gt;SimpleJpaRepository&lt;/code&gt;까지 하기위한 transaction을 얻게된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에러가 나는 부분&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리의 코드에서는 save 이후 &lt;code&gt;throw new RuntimeException()&lt;/code&gt;이 있다.&lt;br /&gt;여기서 부터 after Exception : java.lang ...으로 보이는 rollback 마킹이 존재한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:752) ~[spring-tx-5.3.22.jar:5.3.22]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711) ~[spring-tx-5.3.22.jar:5.3.22]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654) ~[spring-tx-5.3.22.jar:5.3.22]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407) ~[spring-tx-5.3.22.jar:5.3.22]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.22.jar:5.3.22]
    ...생략
    at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 도대체 어떻게 동작하는것인가?&lt;br /&gt;추상클래스인 TransactionAspectSupport.invokeWithinTransaction 메소드를 사용하는 &lt;code&gt;TransactionInterceptor&lt;/code&gt;의 invoke를 호출해서 안쪽에 추상클래스의 메소드를 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74235102/221577991-da0b5a11-0ab7-4ca3-80b9-87777ed4641d.png&quot; alt=&quot;스크린샷 2023-02-27 오후 10 37 23&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 &lt;code&gt;@Transactional&lt;/code&gt;이 붙은 메소드에서 던져진 exception을 Throwable 객체로 catch절에서 받고있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74235102/221578710-508260d5-83c9-417c-92d4-6a3f8fa2ad1a.png&quot; alt=&quot;스크린샷 2023-02-27 오후 10 40 03&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DefaultTransactionAttribute&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74235102/221579391-cd4e7504-a984-4b8f-9c32-a34fccf4c691.png&quot; alt=&quot;스크린샷 2023-02-27 오후 10 43 31&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;rollbackOn 메소드&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74235102/221584413-92e3e282-1797-440e-abc8-3bed91927c58.png&quot; alt=&quot;스크린샷 2023-02-27 오후 11 04 58&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스프링 기본 구성인 DefaultTransactionAttribute구현에서 rollbackOn 메소드를 사용하게 되는데&lt;br /&gt;이 rollbackOn에서 그토록 얘기했던 말들이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;RuntimeException이 여기서 채택되어 instanceof로 체크하고 있다!!!&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 rollback 마크를 하나를 진행해두고 나머지 트랜잭션 어노테이션에 대해서도 commit을 할거냐 rollback을 할거냐에 대한 작업을 이 해당 aop를 통해 동작한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TransactionManager&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 트랜잭션 매니저들은 AbstractPlatformTransactionManager 추상클래스를 상속하여 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74235102/221581739-94d71516-4d40-4af0-8a64-9dc0e68ea576.png&quot; alt=&quot;스크린샷 2023-02-27 오후 10 53 18&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 트랜잭션 매니저가 전부 commit을 수행하는데,&lt;br /&gt;rollback 전용 마크가 하나라도 붙게되면 UnexpectedRollbackException 이 에러가 나타나게 된다.&lt;br /&gt;그래서 보이게되는 예외 메시지가 &lt;code&gt;Transaction silently rolled back because it has been marked as rollback-only&lt;/code&gt;로 나오게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그럼 Stream Closed 는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ㅋㅋㅋㅋ.. 그러게 말이다. 이거 왜뜬건지 도저히 이해가 되지 않는데, dev환경에선 이 로그만 보였지 위의 로그가 안보였었다.&lt;br /&gt;근데 로컬에서 이 부분을 수정하니 말끔히 해결됐다.&lt;br /&gt;아무튼 원초적인 문제는 이 무지성 붙이기 &lt;code&gt;@Transactional&lt;/code&gt;이었다.&lt;br /&gt;막판에 이게 떠올라서 지웠더니 해결되서 꿀잠각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 &lt;code&gt;@Transactional&lt;/code&gt;에 대한 디버깅 정리를 해본다.&lt;/p&gt;</description>
      <category>디버깅</category>
      <category>Spring</category>
      <category>트랜잭션</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/132</guid>
      <comments>https://lsj8367.tistory.com/entry/Transactional-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B3%A0%EC%93%B0%EA%B8%B0#entry132comment</comments>
      <pubDate>Mon, 27 Feb 2023 23:02:18 +0900</pubDate>
    </item>
    <item>
      <title>2022년 회고</title>
      <link>https://lsj8367.tistory.com/entry/2022%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1일1커밋 회고나 다른 회고는 작성했으면서 정작 연말에 한번씩 작성하는 회고는 이번이 처음인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어제는 회사에서 백엔드 팀끼리 회고를 진행했다!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KPT 방식으로 진행을 했었는데, 바빠서 못했던 것들 그리고 문제적으로 좀 느끼고 있던 것들 위주로 Try 에서 3가지가 꼽혔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 테스트코드 2. 레플리카 DB 활용 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;레플리카 DB는 구성은 되어있으나 제대로 활용하지 않고 있었는데, 이번 기회에 내가 건의하여 이슈를 수면위로 드러냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 의견이 동일하셔서 안건이 채택됐다.&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;올 초반에는 내가 다른회사에서 일을 진행했다.&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;그래서 깃 커밋 로그를 좀 뒤져봤다 ㅋㅋㅋㅋㅋㅋ&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;1월&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lsj8367.tistory.com/entry/Mock-%EC%82%AC%EC%9A%A9%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://lsj8367.tistory.com/entry/Mock-%EC%82%AC%EC%9A%A9%EA%B8%B0&lt;/a&gt; 이 글이 1월에 작성했던 글이다.&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;a href=&quot;https://github.com/TEAM-ARK/HTTP_The_Definitive_Guide&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;HTTP 완벽 가이드 스터디&lt;/a&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;2월에는 자료구조, 알고리즘 그리고 디자인패턴에 관심을 많이 가졌던 한 때이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 테스트코드를 먼저 작성한 기반들을 토대로 리팩토링을 진행하면서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인패턴을 적용시키거나, 아키텍처 관점에서 분리할 수 있는 방법들을 계속해서 생각해냈던것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때 만들었던게 벨로그로 옮기면서 &lt;a href=&quot;https://velog.io/@lsj8367/Facade-Pattern-%EC%A0%81%EC%9A%A9%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;퍼사드 패턴 적용기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672367322191&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Facade Pattern 적용기&quot; data-og-description=&quot;퍼사드 패턴에 대한 설명은 깃허브 에 있다.이직하고 회사를 옮기면서 지금의 커머스 서비스 회사에 입사했다.리팩토링 진행도중에 좀 좋은 구조를 구성해서 적어본다.우선 Service 레이어에서 Re&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@lsj8367/Facade-Pattern-%EC%A0%81%EC%9A%A9%EA%B8%B0&quot; data-og-url=&quot;https://velog.io/@lsj8367/Facade-Pattern-적용기&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/A4wUG/hyQ5qM3ijk/4MhVXPhcamvk3Hqk33ocnk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/ehV7Up/hyQ5ER4Aoj/YyzDLKPWFY7QXeax7tGa7K/img.png?width=871&amp;amp;height=550&amp;amp;face=0_0_871_550,https://scrap.kakaocdn.net/dn/UNLvL/hyQ5wfpHTn/bDao5RV7bHqXdq7RsKq7QK/img.png?width=645&amp;amp;height=296&amp;amp;face=0_0_645_296&quot;&gt;&lt;a href=&quot;https://velog.io/@lsj8367/Facade-Pattern-%EC%A0%81%EC%9A%A9%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@lsj8367/Facade-Pattern-%EC%A0%81%EC%9A%A9%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/A4wUG/hyQ5qM3ijk/4MhVXPhcamvk3Hqk33ocnk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/ehV7Up/hyQ5ER4Aoj/YyzDLKPWFY7QXeax7tGa7K/img.png?width=871&amp;amp;height=550&amp;amp;face=0_0_871_550,https://scrap.kakaocdn.net/dn/UNLvL/hyQ5wfpHTn/bDao5RV7bHqXdq7RsKq7QK/img.png?width=645&amp;amp;height=296&amp;amp;face=0_0_645_296');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Facade Pattern 적용기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;퍼사드 패턴에 대한 설명은 깃허브 에 있다.이직하고 회사를 옮기면서 지금의 커머스 서비스 회사에 입사했다.리팩토링 진행도중에 좀 좋은 구조를 구성해서 적어본다.우선 Service 레이어에서 Re&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;3월 ~ 4월&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이부분엔 되게 뭘 한게 없는것같다 라고 생각했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 친구의 권유로 스프링 스터디 모임에 나가게됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;김영한님의 스프링 고급편 + 토비의 스프링 1권을 읽는 스터디를 나가면서 많은 대화들을 나눴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 회사에 근무하는 분들과 대화해서 좋았던 자리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 스프링이 끝난 후에도 Real Mysql 8.0 서적을 읽는 스터디도 진행했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 DB에 대해 좀 더 깊게 공부했던 때가 아닐까 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.notion.so/lsj8367/18612d3c37db43f6a54c6305ff5a06ba&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;책정리&lt;/a&gt; 는 노션에서 주로 했다.&lt;/p&gt;
&lt;figure id=&quot;og_1672367657656&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;책 읽기&quot; data-og-description=&quot;책 추가하기&quot; data-og-host=&quot;www.notion.so&quot; data-og-source-url=&quot;https://www.notion.so/lsj8367/18612d3c37db43f6a54c6305ff5a06ba&quot; data-og-url=&quot;https://www.notion.so/18612d3c37db43f6a54c6305ff5a06ba&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b3IbLD/hyQ5wmaIJi/KLnUmtGETLIlBaK9azpsfK/img.jpg?width=2000&amp;amp;height=1328&amp;amp;face=0_0_2000_1328,https://scrap.kakaocdn.net/dn/b3LyVf/hyQ5t35isp/mK4Avg0WYYEs8CnjxnuLiK/img.jpg?width=2000&amp;amp;height=1328&amp;amp;face=0_0_2000_1328&quot;&gt;&lt;a href=&quot;https://www.notion.so/lsj8367/18612d3c37db43f6a54c6305ff5a06ba&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.notion.so/lsj8367/18612d3c37db43f6a54c6305ff5a06ba&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b3IbLD/hyQ5wmaIJi/KLnUmtGETLIlBaK9azpsfK/img.jpg?width=2000&amp;amp;height=1328&amp;amp;face=0_0_2000_1328,https://scrap.kakaocdn.net/dn/b3LyVf/hyQ5t35isp/mK4Avg0WYYEs8CnjxnuLiK/img.jpg?width=2000&amp;amp;height=1328&amp;amp;face=0_0_2000_1328');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;책 읽기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;책 추가하기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.notion.so&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속해서 책을 읽었던것 같다. DDD... ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5월&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5월은 이제 삽질을 많이했던 시기였다.&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;a href=&quot;https://github.com/lsj8367/semina-list&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;세미나 자료&lt;/a&gt;는 이때 DDD, 스프링 배치, 단위테스트 였다.&lt;/p&gt;
&lt;figure id=&quot;og_1672367850478&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - lsj8367/semina-list&quot; data-og-description=&quot;Contribute to lsj8367/semina-list development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/lsj8367/semina-list&quot; data-og-url=&quot;https://github.com/lsj8367/semina-list&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hQUqA/hyQ5FDr6jI/NcwoTmc9yMPRnr7nA5AyN1/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234&quot;&gt;&lt;a href=&quot;https://github.com/lsj8367/semina-list&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/lsj8367/semina-list&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hQUqA/hyQ5FDr6jI/NcwoTmc9yMPRnr7nA5AyN1/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - lsj8367/semina-list&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to lsj8367/semina-list development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;a href=&quot;https://velog.io/@lsj8367/Spring-Batch-%EB%B0%B0%EC%B9%98-%EC%97%90%EB%9F%AC-%EA%B0%9C%EC%84%A0%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;배치에서 나는 에러&lt;/a&gt;를 개선했다!!!&lt;/p&gt;
&lt;figure id=&quot;og_1672367777933&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring Batch] 배치 에러 개선기&quot; data-og-description=&quot;업무에서 Spring Batch로 세미나를 진행하고, 앱 푸시 기능을 배치로 전환하는 작업을 진행했다.&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@lsj8367/Spring-Batch-%EB%B0%B0%EC%B9%98-%EC%97%90%EB%9F%AC-%EA%B0%9C%EC%84%A0%EA%B8%B0&quot; data-og-url=&quot;https://velog.io/@lsj8367/Spring-Batch-배치-에러-개선기&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/YSG2S/hyQ5B17xGM/2x2aIswivT0JUtgsZtAcGk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/b1tPWy/hyQ5B8TOli/XRNlpYjhXkIMSp8QFuQQZ0/img.png?width=421&amp;amp;height=421&amp;amp;face=105_133_315_362&quot;&gt;&lt;a href=&quot;https://velog.io/@lsj8367/Spring-Batch-%EB%B0%B0%EC%B9%98-%EC%97%90%EB%9F%AC-%EA%B0%9C%EC%84%A0%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@lsj8367/Spring-Batch-%EB%B0%B0%EC%B9%98-%EC%97%90%EB%9F%AC-%EA%B0%9C%EC%84%A0%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/YSG2S/hyQ5B17xGM/2x2aIswivT0JUtgsZtAcGk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/b1tPWy/hyQ5B8TOli/XRNlpYjhXkIMSp8QFuQQZ0/img.png?width=421&amp;amp;height=421&amp;amp;face=105_133_315_362');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring Batch] 배치 에러 개선기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;업무에서 Spring Batch로 세미나를 진행하고, 앱 푸시 기능을 배치로 전환하는 작업을 진행했다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 쿼리 속도 개선했던 작업도 있었고, 이게 바로 Real Mysql 서적을 같이 공부하면서 인덱스를 내가 적용했던 시점이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6월엔 운영체제 강의를 들었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어서 블로그를 이제 티스토리로 옮기게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 옮겼던 이유는 벨로그에선 전체 통계를 볼 수 없었던게 너무 아쉬웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 티스토리로 옮겨왔는데, 벨로그에 있는 글이 훨씬 사람들 방문이 많은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 옮겨가고 싶은 느낌도 들기는 한다...ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 7월에 공백기간을 좀 거친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 했던것은 넥스트 스텝의 ATDD과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전에 TDD, Clean Code With Java 과정을 할 때에는 바빠서 마지막 볼링구간을 이수하지 못했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 얻어가는 것은 많았는데 ATDD는 전부 완주를 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lsj8367.tistory.com/entry/ATDD-%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C-with-Spring-5%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ATDD회고&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672368322630&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;ATDD, 클린 코드 with Spring 5기 수료 회고&quot; data-og-description=&quot;이전에 TDD, Clean Code with Java 12기를 이수하면서, 테스트 코드에대한 중요성 그리고 단위 테스트는 어떻게 해야겠다! 라고 깨달음을 얻었었다. 그렇게 하면서 업무에도 테스트코드를 적용하려고 &quot; data-og-host=&quot;lsj8367.tistory.com&quot; data-og-source-url=&quot;https://lsj8367.tistory.com/entry/ATDD-%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C-with-Spring-5%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0&quot; data-og-url=&quot;https://lsj8367.tistory.com/entry/ATDD-%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C-with-Spring-5%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bphO5m/hyQ5vgwL86/fdmlPn5oxibWKs3KZ4uHrk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/fU0e9/hyQ5uaRpeV/twijmelVj2xtjDxjXYvJq1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/KUZD2/hyQ5AvmSus/j0gFaidSLa6qDEkPmwL32k/img.jpg?width=750&amp;amp;height=797&amp;amp;face=0_0_750_797&quot;&gt;&lt;a href=&quot;https://lsj8367.tistory.com/entry/ATDD-%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C-with-Spring-5%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lsj8367.tistory.com/entry/ATDD-%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C-with-Spring-5%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bphO5m/hyQ5vgwL86/fdmlPn5oxibWKs3KZ4uHrk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/fU0e9/hyQ5uaRpeV/twijmelVj2xtjDxjXYvJq1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/KUZD2/hyQ5AvmSus/j0gFaidSLa6qDEkPmwL32k/img.jpg?width=750&amp;amp;height=797&amp;amp;face=0_0_750_797');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ATDD, 클린 코드 with Spring 5기 수료 회고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전에 TDD, Clean Code with Java 12기를 이수하면서, 테스트 코드에대한 중요성 그리고 단위 테스트는 어떻게 해야겠다! 라고 깨달음을 얻었었다. 그렇게 하면서 업무에도 테스트코드를 적용하려고&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;lsj8367.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때의 나는 퇴사를 결심한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면서 핀테크로 도메인을 옮기게된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8월&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8월에 드디어 모든 글을 티스토리로 옮기고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lsj8367.tistory.com/entry/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%98%AE%EA%B8%B0%EA%B3%A0-%EC%B5%9C%EC%8B%A0-%EA%B7%BC%ED%99%A9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;근황&lt;/a&gt;을 쓴다.&lt;/p&gt;
&lt;figure id=&quot;og_1672368172571&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;블로그를 옮기고 최신 근황&quot; data-og-description=&quot;블로그를 기존 벨로그에서 티스토리로 옮기게 되었다. 그래서 거기서 포스팅 했던 글들을 전부 지금 이 티스토리로 옮기고 드디어!! 진짜 날짜에 맞는 첫글을 써본다. 이직 우선, 전 직장에서 6&quot; data-og-host=&quot;lsj8367.tistory.com&quot; data-og-source-url=&quot;https://lsj8367.tistory.com/entry/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%98%AE%EA%B8%B0%EA%B3%A0-%EC%B5%9C%EC%8B%A0-%EA%B7%BC%ED%99%A9&quot; data-og-url=&quot;https://lsj8367.tistory.com/entry/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%98%AE%EA%B8%B0%EA%B3%A0-%EC%B5%9C%EC%8B%A0-%EA%B7%BC%ED%99%A9&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ehmwrh/hyQ5B17W0x/mGyqRDZKaJYXukJCaN95m0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/jmMeu/hyQ5vOlDtW/dEuhAUJnaeIwY3Uph2BuBK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://lsj8367.tistory.com/entry/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%98%AE%EA%B8%B0%EA%B3%A0-%EC%B5%9C%EC%8B%A0-%EA%B7%BC%ED%99%A9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lsj8367.tistory.com/entry/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%98%AE%EA%B8%B0%EA%B3%A0-%EC%B5%9C%EC%8B%A0-%EA%B7%BC%ED%99%A9&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ehmwrh/hyQ5B17W0x/mGyqRDZKaJYXukJCaN95m0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/jmMeu/hyQ5vOlDtW/dEuhAUJnaeIwY3Uph2BuBK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;블로그를 옮기고 최신 근황&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;블로그를 기존 벨로그에서 티스토리로 옮기게 되었다. 그래서 거기서 포스팅 했던 글들을 전부 지금 이 티스토리로 옮기고 드디어!! 진짜 날짜에 맞는 첫글을 써본다. 이직 우선, 전 직장에서 6&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;lsj8367.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;8월에 인프콘을 다녀왔었는데, 응모한 것에선 당첨이 되지 않았고,&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;a href=&quot;https://lsj8367.tistory.com/entry/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%EC%82%AC%EC%9A%A9%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;라즈베리파이로 서버를 구축&lt;/a&gt;하면서 짧게 인프콘을 올렸었다.&lt;/p&gt;
&lt;figure id=&quot;og_1672368499383&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;라즈베리파이 사용&quot; data-og-description=&quot;시작하기 내가 클라우드 요금은 견딜 수 없어서 상당히 겁이 난 상태였다. 하지만 뭔가 내가 만들어서 써보고 싶은게 최근에 생기게 됐다. 그게 바로 북마크인데, 크롬에 의존해서 북마크를 하&quot; data-og-host=&quot;lsj8367.tistory.com&quot; data-og-source-url=&quot;https://lsj8367.tistory.com/entry/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%EC%82%AC%EC%9A%A9%EA%B8%B0&quot; data-og-url=&quot;https://lsj8367.tistory.com/entry/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%EC%82%AC%EC%9A%A9%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/C5NqL/hyQ5wNigvr/q607O7k22svGgqTK8k3lmk/img.jpg?width=800&amp;amp;height=850&amp;amp;face=0_0_800_850,https://scrap.kakaocdn.net/dn/bqP8QT/hyQ5tbWDM7/8saVFVMg6YKpsCT7fhaJDK/img.jpg?width=800&amp;amp;height=850&amp;amp;face=0_0_800_850,https://scrap.kakaocdn.net/dn/jI1qP/hyQ5xZI905/KAJosCr7EFUBb5hNVAsR60/img.jpg?width=953&amp;amp;height=1013&amp;amp;face=0_0_953_1013&quot;&gt;&lt;a href=&quot;https://lsj8367.tistory.com/entry/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%EC%82%AC%EC%9A%A9%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lsj8367.tistory.com/entry/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%EC%82%AC%EC%9A%A9%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/C5NqL/hyQ5wNigvr/q607O7k22svGgqTK8k3lmk/img.jpg?width=800&amp;amp;height=850&amp;amp;face=0_0_800_850,https://scrap.kakaocdn.net/dn/bqP8QT/hyQ5tbWDM7/8saVFVMg6YKpsCT7fhaJDK/img.jpg?width=800&amp;amp;height=850&amp;amp;face=0_0_800_850,https://scrap.kakaocdn.net/dn/jI1qP/hyQ5xZI905/KAJosCr7EFUBb5hNVAsR60/img.jpg?width=953&amp;amp;height=1013&amp;amp;face=0_0_953_1013');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;라즈베리파이 사용&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;시작하기 내가 클라우드 요금은 견딜 수 없어서 상당히 겁이 난 상태였다. 하지만 뭔가 내가 만들어서 써보고 싶은게 최근에 생기게 됐다. 그게 바로 북마크인데, 크롬에 의존해서 북마크를 하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;lsj8367.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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;9, 10, 11월&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9월에 기억나는건 &lt;a href=&quot;https://github.com/back-end-study/effective-java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이펙티브 자바 스터디&lt;/a&gt;를 참여하게 된다.&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;2회독을 스터디로 하게 되면서 공유한게 많은 것 같다.&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;이 때는 내가 처음으로 Kafka 를 사용하게 되는 날이 온다!&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;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;p data-ke-size=&quot;size16&quot;&gt;지금 회사에선 백엔드 개발자 분들이 나를 포함해 8명이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한분은 프로그래밍 동아리를 통해 마켓컬리로 이직하셨다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면서 회고를 쓰는 지금 신입 백엔드 개발자분이 한분 입사하여 8명이 변함없는 상태가 되었다 ㅋㅋㅋ&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;11월엔 NHN Forward 컨퍼런스도 다녀왔고, 여기서 인상깊었던건 아무래도 DDD + Clean Architecture&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세션을 너무 재미있게 들었었다. 아는 내용이기도 하고 지금 관심갖고 있던 터였기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSA 구조로 회사가 전환을 하면서 기존의 계층형 아키텍처를 탈피하며 도메인 단위로 뭔가 구성을 해보고 싶어서 많이 들여다 본게 컸다.&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;12월&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이펙티브 자바 스터디는 계속 유지하면서 알고리즘을 지속적으로 월, 목 마다 2문제씩 풀이하는 스터디를 한분과 진행중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 설계하는데에 조금 더 생각하는게 깊어진 효과를 보는중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아 참, 이때부터 까치산의 모각코를 나갔다 ㅋㅋㅋㅋㅋ 잘하시는 고수분들 많이 만났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무에 많이 힘을 쏟았던 월이고, 오늘은 마지막날이라 휴가를 쓰고 쉬고있다 !!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 12월에 좀 내가 회사에 정말 적응을 했다고 느끼는건 이제 사람들과 많이 대화하고 있다는것. 장난도 치며 웃기도 하고 소통도 활발해진것.&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;끝으로&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 해를 다 정리해버렸다. 이번 년도에는 6개월마다 이력서를 써보는 것을 많이 못해봤다.&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;p data-ke-size=&quot;size16&quot;&gt;요즘은 &lt;a href=&quot;https://www.notion.so/lsj8367/Todo-List-a43ad9b1003d48e8910c03e9deb89068&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;일일 회고&lt;/a&gt;를 작성한다. 업무가 됐든, 주말이 됐든 하루에 뭘 했는지 간략하게 쓰는 작업을 하고 있는데,&lt;/p&gt;
&lt;figure id=&quot;og_1672369250225&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;일일 Todo List&quot; data-og-description=&quot;일일 데일리 해야할 일 그리고 공부할 것을 적어놓는 공간&quot; data-og-host=&quot;www.notion.so&quot; data-og-source-url=&quot;https://www.notion.so/lsj8367/Todo-List-a43ad9b1003d48e8910c03e9deb89068&quot; data-og-url=&quot;https://www.notion.so/a43ad9b1003d48e8910c03e9deb89068&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zqb8m/hyQ5A3ekgU/UScZ64IdnqlkgD5YtgPKK0/img.png?width=2000&amp;amp;height=2256&amp;amp;face=0_0_2000_2256&quot;&gt;&lt;a href=&quot;https://www.notion.so/lsj8367/Todo-List-a43ad9b1003d48e8910c03e9deb89068&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.notion.so/lsj8367/Todo-List-a43ad9b1003d48e8910c03e9deb89068&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zqb8m/hyQ5A3ekgU/UScZ64IdnqlkgD5YtgPKK0/img.png?width=2000&amp;amp;height=2256&amp;amp;face=0_0_2000_2256');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;일일 Todo List&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;일일 데일리 해야할 일 그리고 공부할 것을 적어놓는 공간&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.notion.so&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것도 나중에 1년치를 작성하면 회고를 해보는것도 좋은것같다.&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;뭔가 하나에 잘 정착하지 못했다. 블로그든, 회사든&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 좋은 개발자분들에게 많이 배우고 공부해야될 것도 늘어난 지금 많이 노력해서 내년엔 좀 더 많이 성장을 했으면 좋겠다!!&lt;/p&gt;</description>
      <category>Diary</category>
      <category>2022년 회고</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/131</guid>
      <comments>https://lsj8367.tistory.com/entry/2022%EB%85%84-%ED%9A%8C%EA%B3%A0#entry131comment</comments>
      <pubDate>Fri, 30 Dec 2022 12:03:08 +0900</pubDate>
    </item>
    <item>
      <title>FeignClient Logging level 디버깅</title>
      <link>https://lsj8367.tistory.com/entry/FeignClient-Logging-level</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 서비스들이 여러개로 쪼개져있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리는 주로 FeignClient를 사용하는데, 애를 먹었던 로깅레벨에 대해 포스팅한다.&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;스프링 프레임워크를 사용하고 있기에 여기서 같이 제공해주는 Spring Cloud의 OpenFeign을 사용하였다.&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-filename=&quot;스크린샷 2022-12-16 오후 11.37.03.png&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMgymZ/btrTQwkO6Vz/XitmcfJA65Lhp9kKWX6Vn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMgymZ/btrTQwkO6Vz/XitmcfJA65Lhp9kKWX6Vn1/img.png&quot; data-alt=&quot; 출처 -&amp;amp;amp;nbsp;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMgymZ/btrTQwkO6Vz/XitmcfJA65Lhp9kKWX6Vn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMgymZ%2FbtrTQwkO6Vz%2FXitmcfJA65Lhp9kKWX6Vn1%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;954&quot; height=&quot;524&quot; data-filename=&quot;스크린샷 2022-12-16 오후 11.37.03.png&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; 출처 -&amp;amp;nbsp;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/&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;해당 예시처럼 나는 FeignClient를 구현했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 @EnableFeignClients 는 별도의 Configuration 클래스 파일에 설정을 해주었었다!&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;Feing Logging Level은 총 4단계로 이루어져있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NONE:&lt;span&gt;&amp;nbsp;&lt;/span&gt; 로깅 없음(Default)&lt;/li&gt;
&lt;li&gt;BASIC:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp;요청 방법 및 URL, 응답 상태 코드 및 실행 시간 기록&lt;/li&gt;
&lt;li&gt;HEADERS:&lt;span&gt;&amp;nbsp;&lt;/span&gt; 요청 및 응답 헤더와 함께 기본 정보를 기록&lt;/li&gt;
&lt;li&gt;FULL&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 요청과 응답 모두에 대한 헤더, 본문 및 메타데이터를 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 BASIC, HEADERS에는 정상작동을 했던 우리의 소스였다.&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;일명 스크래핑이라고 하는 기술, 흔히 크롤링이라고도 할 수 있을것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 작업을 외부 API로 연동하여 응답값을 받아 파싱해주는 작업이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 우리의 규칙상 해당 스크래핑의 원문 데이터라는 아예 크롤링의 전체에 해당하는 xml양식의 데이터도 같이 받아오는 방식을 정책으로 정했기 때문에 우리는 Google Cloud 를 사용하기에 Google Cloud Storage에 원문 데이터를 저장하고, 나머지 데이터를 데이터베이스에 저장하는 구조로 구성이 되어있다.&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;길지않고 정상적인 응답을 받는 스크래핑 로직의 경우에는 이런 디버깅이 해당되지 않는다.&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-filename=&quot;IMG_3528.PNG&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkbxwO/btrTRdZl24N/JsMkWQHUZwZgDkj34K0JG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkbxwO/btrTRdZl24N/JsMkWQHUZwZgDkj34K0JG1/img.png&quot; data-alt=&quot;에러 로그.... 정보유출되는 것이 너무 많아 다 지워버렸다 ㅋㅋㅋ...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkbxwO/btrTRdZl24N/JsMkWQHUZwZgDkj34K0JG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkbxwO%2FbtrTRdZl24N%2FJsMkWQHUZwZgDkj34K0JG1%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;1125&quot; height=&quot;2436&quot; data-filename=&quot;IMG_3528.PNG&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&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;에러 메시지를 보면 해당 응답값의 타입이 application/json이 아니라는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디버깅을 찍어보면 정상 json응답값이 파싱이 되고 있었다.&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;아래부분이 좀 더 의심스러웠었는데, buffer Length : 8192&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까... 버퍼길이가 8192 제한인데 길이가 더 커서 담기지 않아서 에러가 발생한다는 것이다.&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;스크래핑 작업 전후로 시작했고 완료되었다는 외부의 로직 실행결과를 로그로 담고 있다.&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;feign 의 기본 클라이언트는 어떤것을 쓰냐면 바로 ApacheHttpClient를 사용한다.&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;아래는 구현체의 내용 일부분이며, 빨간 네모의 asInputStream으로 body를 출력하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ApacheHttpClient.png&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buNTYn/btrTRDcqaEL/N1BumcHPcZrjveBcZpOpyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buNTYn/btrTRDcqaEL/N1BumcHPcZrjveBcZpOpyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buNTYn/btrTRDcqaEL/N1BumcHPcZrjveBcZpOpyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuNTYn%2FbtrTRDcqaEL%2FN1BumcHPcZrjveBcZpOpyK%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;1608&quot; height=&quot;1002&quot; data-filename=&quot;ApacheHttpClient.png&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;httpResponse에서 받는 해당 entity의 content를 찍어내는 것인데, 이 부분에서 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 maxContentLength 가 8192이기 때문에 정상적으로 스트림 반환을 하지 않고 저렇게 예외를 띄우게 되는 것이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분이 TCP 연결에서의 최대 길이이기 때문에 HttpClient를 사용하면 이런 예외가 발생된다.&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;아래는 ByteArrayBody이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 부분에서는 ByteArrayInputStream을 사용하기 때문에 TCP length 제한보다 더 쓸수 있는 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Response.png&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;1462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sulJf/btrTQPRYsSk/mnFk5oX7MHmHZEjJ2YkNYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sulJf/btrTQPRYsSk/mnFk5oX7MHmHZEjJ2YkNYk/img.png&quot; data-alt=&quot;이것이 바로 ByteArrayInputStream....?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sulJf/btrTQPRYsSk/mnFk5oX7MHmHZEjJ2YkNYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsulJf%2FbtrTQPRYsSk%2FmnFk5oX7MHmHZEjJ2YkNYk%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;1782&quot; height=&quot;1462&quot; data-filename=&quot;Response.png&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;1462&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이것이 바로 ByteArrayInputStream....?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무든 이 두 asInputStream()을 보는 이유는 아래에서 설명하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이 ByteArrayInputStream은 tcp max content length와 관계없이 스트림을 출력하기에 길이제한이 없다.&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;이 두개의 차이를 가르는 것은 Log 였다!!!&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;feign 의 Logger 일부분.png&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v54JV/btrTQQwB3yv/WN6JorVkkTVEzWBUHcz5jK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v54JV/btrTQQwB3yv/WN6JorVkkTVEzWBUHcz5jK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v54JV/btrTQQwB3yv/WN6JorVkkTVEzWBUHcz5jK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv54JV%2FbtrTQQwB3yv%2FWN6JorVkkTVEzWBUHcz5jK%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;1596&quot; height=&quot;922&quot; data-filename=&quot;feign 의 Logger 일부분.png&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 로그 부분이 BASIC레벨 보다 수준이 낮은 경우 수행되는 로직인데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 FULL로직에서만 정상적으로 데이터 파싱이 된다.&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;왜? - response.toBuilder().body(bodyData).build() 를 통해 새로운 ByteArrayInputStream으로 생성되기 때문이다.&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;그래서 이 부분을 재정의를 해주어 BASIC 레벨에서도 길이가 아무리 길더라도 데이터를 파싱할 수 있게 설정해주었다.&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;h4 data-ke-size=&quot;size20&quot;&gt;해결방안&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 Feign의 Logger를 상속받아서 log찍는 부분을 override 시켜주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 기존 로직을 또 건드리고 싶지는 않았다. &amp;lt;- 로그는 이대로 찍어주는게 마음 편했으니까?&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;길이가 짧은 응답을 사용했다면 오히려 이런것도 모르고 그냥 설정따라 파싱해주고 안해주고로 넘어갔을 것 같다!!!!&lt;/p&gt;</description>
      <category>디버깅</category>
      <category>FeignClient</category>
      <category>Java</category>
      <category>Spring</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/130</guid>
      <comments>https://lsj8367.tistory.com/entry/FeignClient-Logging-level#entry130comment</comments>
      <pubDate>Sat, 17 Dec 2022 00:02:33 +0900</pubDate>
    </item>
    <item>
      <title>NHN Forward</title>
      <link>https://lsj8367.tistory.com/entry/NHN-Forward</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 삶의 두번째 컨퍼런스인 NHN Forward에 다녀왔다.&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;들어가자마자 체크인을 QR로 진행하고 키노트를 진행하는데 계속 서서 컨퍼런스 장소까지 가니 다리가 아팠는데 또 내내 서있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점심먹고 들었던 세션인 분산 시스템에서 데이터를 전달하는 효율적인 방법 세션에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDB, RabbitMQ, Kafka를 통한 서로 다른 마이크로 서비스들의 트랜잭션을 어떻게 관리할 것인가에 대해 되게 재밌게 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDB로 사용하는 방법을 이번에 처음 보게되어 예전에 면접에서 받았던 질문이 이런걸 물어본 거였구나 하면서 아~ 하고 깨닫는 순간이었다.&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;a href=&quot;https://lsj8367.notion.site/d915a5d61e9941599991ce721d590574&quot;&gt;노션&lt;/a&gt;에서 해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들이 엄청많았고, 아침에 점심 조금 늦게먹고 자리를 안잡았으면 밖에서 그냥 발만 동동 굴렀을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앉지 못하면 자리가 없어서 못들어간다고 밖에서 얘기하시는 스탭분을 봤다. 그래서 다른분들이 참여 못하시는걸 봤었다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼자만 뽑히게되어 아는사람 없이 혼자가서 얌전하게 세션들 다 듣고 바로 와버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 다른 부스인 커리어리에서 앱 다운하면 뭔가 물품을 준다해서 이미 나는 앱이 있어서 저는 이미 앱이 있어요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하니까 귀하신 회원님이라면서... ㅋㅋㅋ 바로 챙겨주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 너무 좋은 시간이었고, 지속적으로 이런 컨퍼런스도 참여하면서 많이 교류하고 정보도 얻어야겠다.&lt;/p&gt;
&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;0&quot; data-origin-height=&quot;0&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bunSQ2/btrR1n4IH0P/krkAS8bQFgmGQdwsczOLHk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bunSQ2/btrR1n4IH0P/krkAS8bQFgmGQdwsczOLHk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bunSQ2/btrR1n4IH0P/krkAS8bQFgmGQdwsczOLHk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbunSQ2%2FbtrR1n4IH0P%2FkrkAS8bQFgmGQdwsczOLHk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Diary</category>
      <category>diary</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/129</guid>
      <comments>https://lsj8367.tistory.com/entry/NHN-Forward#entry129comment</comments>
      <pubDate>Thu, 24 Nov 2022 20:31:31 +0900</pubDate>
    </item>
    <item>
      <title>@Async 사용시 에러 해결</title>
      <link>https://lsj8367.tistory.com/entry/Async-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 포스팅하는데 회사에서 그동안 앱 2.0 버전을 출시한다고 이래저래 바빴던 나날을 보냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로는 만족스러운 출시..? 였던것 같다 ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그도 많았고, QA 엔지니어께서 고생을 많이 하셨을 수도 있고 내가 구현한 메시지 플랫폼도 테스트하기가 정말 까다로웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각설하고..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 에러사항을 구현하는건 Kafka를 이용하지 않아도 되기 때문에 RestAPI로 구현했다. (+ 테스트코드로만)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 코드는 깃허브에 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이번엔 무슨 버그였냐?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-04 오후 5.31.25.png&quot; data-origin-width=&quot;3468&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbJNJt/btrQr71OWWT/kaAyLgK2kFkhYhI4hO4wWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbJNJt/btrQr71OWWT/kaAyLgK2kFkhYhI4hO4wWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbJNJt/btrQr71OWWT/kaAyLgK2kFkhYhI4hO4wWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbJNJt%2FbtrQr71OWWT%2FkaAyLgK2kFkhYhI4hO4wWK%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;3468&quot; height=&quot;1022&quot; data-filename=&quot;스크린샷 2022-11-04 오후 5.31.25.png&quot; data-origin-width=&quot;3468&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 에러가 쏟아져나왔다. 기존 레거시 푸시는 NHN Toast를 이용한 푸시서비스로 구성되어 있었다.&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;h3 data-ke-size=&quot;size23&quot;&gt;Async 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 &lt;code&gt;@Async&lt;/code&gt; 를 사용하기 위해서는 Configuration 클래스를 하나 만들어주고 비동기 실행기에 대한 설정을 해주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74235102/200002393-c1780772-ccd1-47e1-8171-5b49620304d8.png&quot; alt=&quot;스크린샷 2022-11-04 오후 11 40 12&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 에러를 일부러 내기 위해서 큐 사이즈를 5개 기본 스레드 1, 최대 스레드 2, 그리고 비동기로 처리할 큐 사이즈를 5로 구성했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제상황 1&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nhn 푸시를 보내기 위해선 Kafka에 Producer를 통해 메세지를 넣어주면, 내가 구현한 메시지 플랫폼에서 해당 메시지를 Consume하여 동작하는 방식으로 구성되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 나는 비동기로 구현하겠다고만 생각하고 사이즈를 제한해두지는 않았었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Consume은 계속해서 KafkaListener를 통해 무제한으로 하고 있는 와중에 이 컨슘 속도가 너무 빠르니까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설정한 비동기 처리 큐 사이즈를 200으로 잡아놓아도 예를 들어 1000건이 한방에 producer를 통해 적재된 후 바로 consume을 해버리니까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100개 큐를 훌쩍 넘어버려서도 막는게 아니라 지속적 컨슘이 일어나기에 큐 사이즈를 넘어서는 순간에도 이 메소드 실행을 밀어 넣는 오류였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 맨위의 이미지처럼 Exception이 발생하게 되는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에러 직접 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 코드는 깃허브에서 보면 되지만 여기서는 간략하게 대충 구현하도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK 17버전이기 때문에 record class를 사용함. (record를 사용하면 클래스는 final로 선언되며 equals &amp;amp; hashCode를 재정의하여 갖고있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/hello 를 호출하면 printAsync라는 비동기 메소드를 100번호출하게끔 만드는 로직이 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HelloController.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
public record HelloController(HelloService helloService) {

    @GetMapping(&quot;/hello&quot;)
    public ResponseEntity&amp;lt;?&amp;gt; hello() {

        int i = 0;
        while (true) {
            helloService.printAsync();
            i++;

            if (i == 100) {
                break;
            }
        }

        return ResponseEntity.ok(Map.of(&quot;data&quot;, &quot;success&quot;));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HelloService.java&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Slf4j
@Service
public class HelloService {

    @Async(&quot;threadPoolTaskExecutor&quot;)
    public void printAsync() {
        try {
            Thread.sleep(5000);
            log.info(Thread.currentThread().getName());
            log.info(&quot;hello service print!!!&quot;);
        } catch (InterruptedException e) {
            log.error(&quot;error : {}&quot;, e.getMessage());
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;printAsync()는 비동기로 호출하며 5초를 기다리고 log를 찍게 되는데, 우리가 처음 전역적으로 설정해준 스레드 설정은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청이 들어오면 maxQueue 사이즈인 5까지만 담을 수 있게 되어있고 작업이 느려서 큐 데이터를 하나 소진하지 못한다면 에러가 발생하는데&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Caused by: org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@265c1a7c[Running, pool size = 2, active threads = 2, queued tasks = 5, completed tasks = 0]] did not accept task: org.springframework.aop.interceptor.AsyncExecutionInterceptor$$Lambda$967/0x00000008010a7dc0@740a0d5e
    at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.submit(ThreadPoolTaskExecutor.java:391)
    at org.springframework.aop.interceptor.AsyncExecutionAspectSupport.doSubmit(AsyncExecutionAspectSupport.java:292)
    at org.springframework.aop.interceptor.AsyncExecutionInterceptor.invoke(AsyncExecutionInterceptor.java:129)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
    at com.github.lsj8367.application.HelloService$$EnhancerBySpringCGLIB$$7321912.printAsync(&amp;lt;generated&amp;gt;)
    at com.github.lsj8367.presentation.HelloController.hello(HelloController.java:17)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1070)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    ... 87 more
Caused by: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@29013ef2[Not completed, task = org.springframework.aop.interceptor.AsyncExecutionInterceptor$$Lambda$967/0x00000008010a7dc0@740a0d5e] rejected from java.util.concurrent.ThreadPoolExecutor@265c1a7c[Running, pool size = 2, active threads = 2, queued tasks = 5, completed tasks = 0]
    at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)
    at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)
    at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365)
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
    at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.submit(ThreadPoolTaskExecutor.java:388)
    ... 107 more&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 봤던 이미지와 같은 예외가 발생하는걸 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까 큐 공간이 가득차기 전에 계속해서 비동기를 호출하며 작업큐에 메소드를 태우려고 하다보니 이런 예외가 발생한 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결은?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 나는 일단 푸시 발송이 그렇게 오래걸리는 로직이 아니라서 일단은 &lt;b&gt;동기&lt;/b&gt; 상태로 바꿔놓았지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지속적인 모니터링을 하여 트래픽이 많아지게 되면 그때는 batchListener를 적용하여 비동기로 여러개를 한방에 처리하는 방법을 고려중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 이 부분은 카프카의 리밸런싱을 고려해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 내가 생각했던 여러 추측중에 하나가 걸리게 되니까 좀 재밌었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 오늘도 해결했다!&lt;/p&gt;</description>
      <category>디버깅</category>
      <category>Java</category>
      <category>Spring</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/128</guid>
      <comments>https://lsj8367.tistory.com/entry/Async-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0#entry128comment</comments>
      <pubDate>Fri, 4 Nov 2022 23:56:11 +0900</pubDate>
    </item>
    <item>
      <title>Kafka Offset Commit의 중요성</title>
      <link>https://lsj8367.tistory.com/entry/Kafka-Offset-Commit%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저번 포스팅의 마지막 마무리가 바로 이 에러를 찾지 못했던 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kafka 에러를 고치게 된 시점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 특정 &lt;code&gt;Consumer&lt;/code&gt;만 consume을 못한다고 했었는데, 이는 당연 잘못된 것이었다!!!&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;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@KafkaListener(topics = &quot;topic&quot;, properties = {
        &quot;spring.kafka.consumer.properties.spring.json.type.mapping=com.github.lsj8367.MessageReq&quot;
    })
public void consumeSomething(final MessageReq req, Acknowledgment acknowledgment) {
    pushService.sendPush(req);
    acknowledgment.acknowledge();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 회사의 카프카 푸시 로직에서는 자동커밋을 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring kafka 에서는 &lt;code&gt;enable.auto.commit=true&lt;/code&gt; 이라는 설정을 통해 자동으로 일정 시간이 지나면 커밋하게 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 시간은 &lt;code&gt;auto.commit.interval.ms&lt;/code&gt;라는 옵션을 통해 설정을 진행할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;중복이 생기는 현상&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;max.poll.records로 15개를 가져온다고 가정한다. (왜냐면 내가 15개씩 가져와서 스레드 15개로 한번에 할당하고 있으니까 15개로 했다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 한번의 컨슘을 통해 이 record들을 15개를 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 작업하다가 한 7개 작업을 했다(실질적인 푸시까지 완료) 아직 커밋은 일어나지 않았다. 커밋시간은 조금 더 뒤에 일어나는 상황&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;b&gt;배치 서버에서 파티션 1개로 하니까 너무 느리게 적재되는 것 같아요 병렬처리 하게 파티션을 n개로 바꿀 필요성이 있습니다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 하면서 파티션을 늘리는 안이 통과되어 바로 파티션을 늘린다. (참고 - 파티션은 늘릴 수만 있고 줄일 수는 없다)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리밸런싱 시작....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 8개가 아직 작업이 남았지만 컨슘이 멈추며 리밸런싱을 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어? 커밋된게 없네 해서 이전 15개를 다시 읽어 재처리를 진행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 남은 이 8개는 중복이 아니지만, 앞서 작업했던 7개의 메시지는 다시 작업을 수행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 중복이 발생한다고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그럼 넌 뭐가 문제였는데?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 애초에 auto commit이 아닌 수동커밋으로 작업을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;푸시지만 중복으로 발생되면 안되는 그런 푸시 정보이기에 수동으로 작업을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Spring kafka의 기본값이 수동 커밋인 &lt;code&gt;enable.auto.commit=false&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 ackMode를 설정해줄 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ackMode는 뭐냐면 KafkaListener의 offset에 대한 commit을 어떻게 줄것인가를 설정하는 모드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 기본 AckMode는 BATCH이며, 나는 manual_immidiate로 작업했다.&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;721&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rOj2Q/btrNWqLsYZJ/7uoiWTTKu85KgkriXWmof0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rOj2Q/btrNWqLsYZJ/7uoiWTTKu85KgkriXWmof0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rOj2Q/btrNWqLsYZJ/7uoiWTTKu85KgkriXWmof0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrOj2Q%2FbtrNWqLsYZJ%2F7uoiWTTKu85KgkriXWmof0%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;721&quot; height=&quot;783&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;783&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BATCH
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;poll() 메서드로 호출된 레코드가 모두 처리된 이후 커밋&lt;/li&gt;
&lt;li&gt;스프링 카프카 컨슈머의 AckMode 기본값&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MANUAL_IMMEDIATE
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Acknowledgment가 승인되면 즉시 commit&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두가지의 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 현상은 아무런 오류도, 로그도 찍어지지 않는데, 카프카 Producer 메세지를 발행하면 LAG로 적재되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 토픽은 정상적으로 컨슘이 되는게 확인이 된게 너무 억울했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 100개를 연속으로 발행해도 LAG로 메시지가 쌓여서 처리를 못하는 현상이 발생해서 2주를 머리쥐어짰다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 토픽으로 받아봐도 결과가 계속 같아서 너무 짜증이 더 났었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카도 새로 설치해달라고 데브옵스분께 요청했고 그래서 이래저래 너무 힘들었다. (운이 좋았던건 dev환경의 k8s설정이 좀 이상하게 되어 다시 구성해야 되는 상황에 요청드렸던 운이 좋았다 )&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에러난 지점은...&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Slf4j
@Service
public class PushService {

    public void sendPush(final MessageReq req, Acknowledgment acknowledgment) {
        try {
            //푸시로직이 작성되어있다...
            log.info(&quot;push Success : {}&quot;, req);
        } catch (SendFailureException e) {
            log.error(&quot;push failure : {}&quot;, e.getMessage());
            //DLT 처리
        }
        acknowledgment.acknowledge();  // 에러지점
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 예시로 푸시로직이 있다고 가정하고 이렇게 작성하면 당연히 푸시를 보내고 ack를 통해 offset을 전진시켜주었다.&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;code&gt;SendFailureException&lt;/code&gt;이 발생하지 않고 NPE나 다른 예외가 뜨게 되면 이게 메세지 처리가 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 예외가 났으니 offset을 전진 안시켰기 때문에 다시 메시지에 적재 되는것이다.&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;지나가며 보던 블로그의 글들을 보며 commit은 항상 필수적으로 해줘야 한다는게 있었는데 난 당연히 commit을 무조건 하는것으로 생각한 나의 실수였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 LAG에 고정으로 쌓여있냐? 그건 아니라는 소리다. 수치로는 n개가 적재된거로 보이겠지만 0으로 갔다가 다시 쌓였다가 할것 같은 나의 생각이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 간단하다. 위에서 봤을때 문제점이 무엇이었는가?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 offset을 무조건 전진시켜주면 끝난다!!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Slf4j
@Service
public class PushService {

    public void sendPush(final MessageReq req, Acknowledgment acknowledgment) {
        try {
            //푸시로직이 작성되어있다...
            log.info(&quot;push Success : {}&quot;, req);
        } catch (SendFailureException e) {
            log.error(&quot;push failure : {}&quot;, e.getMessage());
            //DLT 처리
        } catch (Exception e) {
            log.error(&quot;알 수 없는 오류 발생 : {}&quot;, e.getMessage());
        }finally {
            acknowledgment.acknowledge();  // 에러지점
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 무조건 commit 처리하게 해주고 저런 예외가 터졌을 경우에는 DLT (Dead Letter Topic)에 적재해주고 후속 처리를 진행하면 되겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무턱대고 구현할 때 좀 더 꼼꼼하게 작성하고 좀 더 테스트를 빡세게 하고 테스트 코드도 좀 더 케이스를 많이 짜려고 노력을 좀 해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 이걸 2주동안 붙잡고 있던게 결국 이런 오류들은 어려운 오류가 아닌데에서 나오는 기간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 기초를 좀 더 탄탄히 하고 항상 꼼꼼하게 하자  &lt;/p&gt;</description>
      <category>Spring/Kafka</category>
      <category>Java</category>
      <category>Kafka</category>
      <category>springboot</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/127</guid>
      <comments>https://lsj8367.tistory.com/entry/Kafka-Offset-Commit%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1#entry127comment</comments>
      <pubDate>Thu, 6 Oct 2022 22:46:15 +0900</pubDate>
    </item>
    <item>
      <title>Spring Kafka 좀 더 공통 설정하기</title>
      <link>https://lsj8367.tistory.com/entry/Spring-Kafka-%EC%A2%80-%EB%8D%94-%EA%B3%B5%ED%86%B5-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;일전에 &lt;a href=&quot;https://lsj8367.tistory.com/entry/Spring-Kafka-Deserializer-Class-Not-Found-Exception&quot;&gt;카프카 에러 포스팅&lt;/a&gt; 에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Configuration 설정을 자바 클래스에서 해주었다. 그것도 클래스별로!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 포스팅과 더불어&lt;/p&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;This error handler cannot process 'SerializationException's directly; please consider configuring an 'ErrorHandlingDeserializer' in the value and/or key deserializer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문구도 출력해줬었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 저 에러 포스팅을 보면서 좀 더 공통화할 수 없을까에서 찾아보다가 ErrorHandlingDeserializer 관련 검색을 해보다가 문서에서 찾게 되었던게 있는데,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhGG6H/btrNTMsUNhN/FLfw30cWhCX9nkhxp37V10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhGG6H/btrNTMsUNhN/FLfw30cWhCX9nkhxp37V10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhGG6H/btrNTMsUNhN/FLfw30cWhCX9nkhxp37V10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhGG6H%2FbtrNTMsUNhN%2FFLfw30cWhCX9nkhxp37V10%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;1210&quot; height=&quot;798&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 설정을 이미지로 한번 가져와봤다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 방법을 Listener가 늘어나면 늘어날 수록 고수할 수가 없다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세부적인 사항 외에는 조금 다 yaml로 공통화를 할 수 있지 않을까?? 나는 SpringBoot를 사용하는데!&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;999&quot; data-origin-height=&quot;729&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BiKFY/btrNUGTit56/57G7iy74TERCO1b8cS0lsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BiKFY/btrNUGTit56/57G7iy74TERCO1b8cS0lsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BiKFY/btrNUGTit56/57G7iy74TERCO1b8cS0lsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBiKFY%2FbtrNUGTit56%2F57G7iy74TERCO1b8cS0lsk%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;999&quot; height=&quot;729&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;729&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;yaml 설정&lt;/h3&gt;
&lt;pre data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;spring:
  kafka:
    consumer:
      group-id: test
      auto-offset-reset: latest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
      properties:
        spring:
          json:
            use:
              type:
                headers: false
            trusted:
              packages: '*'
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer

    listener:
      ack-mode: manual_immediate

    retry:
      topic:
        attempts: 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 헤더로서 값을 받지 않겠다 라는것을 선언해주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서처럼&lt;br /&gt;&lt;code&gt;spring.kafka.consumer.properties.spring.json.type.mapping=메시지를 수신할 dto 풀경로&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 전역에서 여러개를 설정해주어도 되지만, 이 부분은 리스너마다 다를것이라고 생각해서 &lt;code&gt;@KafkaListener&lt;/code&gt;마다 설정해주기로 했다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@KafkaListener(properties={
    &quot;max.poll.records=15&quot;, // 한 작업에 15개의 레코드를 컨슘한다.
    &quot;spring.kafka.consumer.properties.spring.json.type.mapping=com.github.lsj8367.MessageReq&quot;
})
public void consumeMessage(final MessageReq request, Acknowledgement ack) {
    // 메세지 처리
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 완성하여 &lt;code&gt;ConsumerConfiguration&lt;/code&gt;을 제거하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 Spring을 쓰는 것이 아니라 이 설정을 한번 더 추상화하여 간편하게 쓰게 해주는 Boot를 쓰게해주는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 Spring처럼 쓰는게 아닌가 싶었는데, 원하는대로 잘 바꾸어 준 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지로 더 전역적으로 쓸 수 있는것은 공통으로 충분히 더 빼주고 특이한 케이스만이 별도의 Configuration 클래스로 빠져서 설정해주어야 하지 않을까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 해결하지 못한 이슈가 있는데, 특정 Topic만 특이하게 한건을 consume하고 나서부터는 consume을 더이상 하지 않는 오류가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 얼른 해결하고 싶다.&lt;/p&gt;</description>
      <category>Spring/Kafka</category>
      <category>Java</category>
      <category>Kafka</category>
      <category>springboot</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/126</guid>
      <comments>https://lsj8367.tistory.com/entry/Spring-Kafka-%EC%A2%80-%EB%8D%94-%EA%B3%B5%ED%86%B5-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0#entry126comment</comments>
      <pubDate>Thu, 6 Oct 2022 00:17:23 +0900</pubDate>
    </item>
    <item>
      <title>Spring Kafka Deserializer Class Not Found Exception</title>
      <link>https://lsj8367.tistory.com/entry/Spring-Kafka-Deserializer-Class-Not-Found-Exception</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현재 회사에서 spring-kafka 를 이용해서 특정 서비스들의 푸시 메세지 이벤트를 받아서&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;&lt;b&gt;하고 잘 넘어갔던 찰나에!!!&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Class Not Found Exception&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜? 이 에러가 났을까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 나는 구독하는쪽 그러니까 Kafka에서는 Consumer 쪽 만을 구현해주었다.&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;내 지식이 부족했던 탓인지는 모르겠지만, 어쨌든 같은 JSON 형태라고 생각해서 클래스가 무엇이던 간에&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;JSON형식만 같다면 Consume해도 괜찮을 것이라고 처음 생각했었다.&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;그래서 발행 모델인 Producer쪽에서는 예를 들면 &lt;code&gt;MessageReq&lt;/code&gt;로 Producer가 보내고&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;받은 부분인 Consumer에서는 &lt;code&gt;PushReq&lt;/code&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;이랬을 때 Consumer 서버를 키면???!!!&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;바로 Class Not Found Exception이 떠버린다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Caused by: org.springframework.messaging.converter.MessageConversionException: failed to resolve class name. Class not found [com.github.lsj8367.message.PushReq]; nested exception is java.lang.ClassNotFoundException: com.github.lsj8367.message.MessageReq
  at org.springframework.kafka.support.converter.DefaultJackson2JavaTypeMapper.getClassIdType(DefaultJackson2JavaTypeMapper.java:138)
  at org.springframework.kafka.support.converter.DefaultJackson2JavaTypeMapper.toJavaType(DefaultJackson2JavaTypeMapper.java:99)
  at org.springframework.kafka.support.serializer.JsonDeserializer.deserialize(JsonDeserializer.java:342)
  at org.apache.kafka.clients.consumer.internals.Fetcher.parseRecord(Fetcher.java:1030)
  at org.apache.kafka.clients.consumer.internals.Fetcher.access$3300(Fetcher.java:110)
  at org.apache.kafka.clients.consumer.internals.Fetcher$PartitionRecords.fetchRecords(Fetcher.java:1250)
  at org.apache.kafka.clients.consumer.internals.Fetcher$PartitionRecords.access$1400(Fetcher.java:1099)
  at org.apache.kafka.clients.consumer.internals.Fetcher.fetchRecords(Fetcher.java:545)
  at org.apache.kafka.clients.consumer.internals.Fetcher.fetchedRecords(Fetcher.java:506)
  at org.apache.kafka.clients.consumer.KafkaConsumer.pollForFetches(KafkaConsumer.java:1269)
  at org.apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.java:1200)
  at org.apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.java:1176)
  at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.pollAndInvoke(KafkaMessageListenerContainer.java:741)
  at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:698)
  at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
  at java.util.concurrent.FutureTask.run(FutureTask.java:266)
  at java.lang.Thread.run(Thread.java:748)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제코드는 &lt;a href=&quot;https://github.com/lsj8367/laboratory/tree/master/kafka/src&quot;&gt;깃허브&lt;/a&gt;에 있지만, producer와 consumer는 각기 다른 서버로 만들어서 진행시켜주어야 한다!! 안그러면 에러 안나고 받지도 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜? Consumer쪽엔 직렬화한 &lt;code&gt;MessageReq&lt;/code&gt;클래스가 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보내는 쪽에서 미리 받는 Consumer쪽의 객체 형식을 Header에 바인딩 해주거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;받는 입장에서 Header가 아닌 Method로 받게 설정해줄 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결책&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 해결책이 무엇이냐!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 잠깐 말했지만 producer에서 header를 설정해주거나 consumer에서 설정을 해주는 방법이 있다고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 서로 정의가 잘 되어있고, 동시에 구현했다면 나는 전자의 방법을 택했을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만, 각자 개발하는 속도가 있었고, 또 다른 작업들을 계속해서 진행해야 하고 이미 돌아가고 있던 배치서버에서 그걸 바꿔서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 배포하기엔 쉽지 않았었다. (사실 말씀드리기도 좀 그랬다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;디버깅&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;890&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5r1Gz/btrMkNfwLML/gM8SnUzoIWn4kIQcGqyX8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5r1Gz/btrMkNfwLML/gM8SnUzoIWn4kIQcGqyX8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5r1Gz/btrMkNfwLML/gM8SnUzoIWn4kIQcGqyX8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5r1Gz%2FbtrMkNfwLML%2FgM8SnUzoIWn4kIQcGqyX8k%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;890&quot; height=&quot;150&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨끝부분에 false를 넣어주었는데, 이는 들어가보면...&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;1094&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br6Kdh/btrMjgDhxck/fLF8EEop3iGdH7Ojcu9oA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br6Kdh/btrMjgDhxck/fLF8EEop3iGdH7Ojcu9oA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br6Kdh/btrMjgDhxck/fLF8EEop3iGdH7Ojcu9oA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr6Kdh%2FbtrMjgDhxck%2FfLF8EEop3iGdH7Ojcu9oA0%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;1094&quot; height=&quot;444&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;initialize에서 해당 boolean값을 사용하고 있기 때문에&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doDMcA/btrMi6HJLxW/V9Z93RHg660vcSSyDq9Hdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doDMcA/btrMi6HJLxW/V9Z93RHg660vcSSyDq9Hdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doDMcA/btrMi6HJLxW/V9Z93RHg660vcSSyDq9Hdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoDMcA%2FbtrMi6HJLxW%2FV9Z93RHg660vcSSyDq9Hdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1086&quot; height=&quot;344&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;boolean값에 의해서 어떤 타입으로 결정하는지가 나온다.&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;797&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr03LS/btrMkugbqXH/TI1MjsKe4EECBPIMrDLNoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr03LS/btrMkugbqXH/TI1MjsKe4EECBPIMrDLNoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr03LS/btrMkugbqXH/TI1MjsKe4EECBPIMrDLNoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr03LS%2FbtrMkugbqXH%2FTI1MjsKe4EECBPIMrDLNoK%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;797&quot; height=&quot;422&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;422&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;공부해야될거 참 많다...&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 내가 혼자 힘으로 이 메세지 서버를 만들면서 정말 재밌게 개발했다.&lt;/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;뭐하나 건들면 바로 공부해야되는게 너무 어렵지만 재밌어서 좋다.&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;</description>
      <category>Spring/Kafka</category>
      <category>Java</category>
      <category>Kafka</category>
      <category>Spring</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/125</guid>
      <comments>https://lsj8367.tistory.com/entry/Spring-Kafka-Deserializer-Class-Not-Found-Exception#entry125comment</comments>
      <pubDate>Fri, 16 Sep 2022 23:55:48 +0900</pubDate>
    </item>
    <item>
      <title>@ModelAttribute, @RequestBody 커맨드 객체</title>
      <link>https://lsj8367.tistory.com/entry/ModelAttribute-RequestBody-%EC%BB%A4%EB%A7%A8%EB%93%9C-%EA%B0%9D%EC%B2%B4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ModelAttribute와 RequestBody의 커맨드 객체 파싱이 다른것을 확인했다.&lt;br /&gt;한번 알아보자! ModelAttribute 동작과정은 덤이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ModelAttribute&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅을 하는 이유는 인자가 많을 경우에 post방식으로 조회를 하는 식으로 구성을 했었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드리뷰중에 이런말이 나왔었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;get방식으로 다른 객체로 묶어서 한번에 받아보는건 어떤가요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수가 많아지면 많아질 수록 수정점이 늘어날것 같아요! 라고 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 무의식적으로 평소에 하던방식처럼 post로 수정하여 커밋하고 수정했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 post로 안바꾸고 get에서 &lt;code&gt;@ModelAttribute&lt;/code&gt; 사용하면 객체로 파싱이 된다는것을 듣고 내가 부족했구나 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 그 부분에서 나와 집에와서 따로 정리하여 포스팅한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 &lt;a href=&quot;https://github.com/lsj8367/laboratory/tree/master/reflection&quot;&gt;깃허브&lt;/a&gt;에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러와 dto 그리고 컨트롤러 테스트 코드를 간략하게 작성했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Debugging&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 디버깅을 돌리면 이런 순서로 진행이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무조건 DisPatcherServlet이 모든 작업을 분산하여 처리 위임을 진행해주는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서의 핵심은 이 아랫부분이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RequestMappingHandlerAdapter&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RequestMappingHandlerAdapter로 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핸들러 메소드를 처리할 수 있는 ArgumentResolver 들과&lt;br /&gt;returnValueHandler들을 넣어준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 넣어준다!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB3FJv/btrL8TnXtb9/B7vXHngauKx1WJDsG71bh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB3FJv/btrL8TnXtb9/B7vXHngauKx1WJDsG71bh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB3FJv/btrL8TnXtb9/B7vXHngauKx1WJDsG71bh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB3FJv%2FbtrL8TnXtb9%2FB7vXHngauKx1WJDsG71bh1%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;951&quot; height=&quot;387&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ServletInvocableHandlerMethod&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ServletInvocableHandlerMethod가 returnValue를 할 수 있는 애로 채택되는데&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1123&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUYtRZ/btrL9ZOfCWe/o6yyldMuAMQFTGl6p6Lju1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUYtRZ/btrL9ZOfCWe/o6yyldMuAMQFTGl6p6Lju1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUYtRZ/btrL9ZOfCWe/o6yyldMuAMQFTGl6p6Lju1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUYtRZ%2FbtrL9ZOfCWe%2Fo6yyldMuAMQFTGl6p6Lju1%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;1123&quot; height=&quot;528&quot; data-origin-width=&quot;1123&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;InvocableHandlerMethod&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InvocableHandlerMethod를 통해 HandlerMethodArgumentResolver를 찾는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFN8Qx/btrL5xeBb39/kJfsxq4Ef7Dn7Pdpt3DzHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFN8Qx/btrL5xeBb39/kJfsxq4Ef7Dn7Pdpt3DzHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFN8Qx/btrL5xeBb39/kJfsxq4Ef7Dn7Pdpt3DzHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFN8Qx%2FbtrL5xeBb39%2FkJfsxq4Ef7Dn7Pdpt3DzHK%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;1185&quot; height=&quot;752&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;752&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCuxie/btrMacGylJm/ePSjx5uDqMW2afzKeH1kXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCuxie/btrMacGylJm/ePSjx5uDqMW2afzKeH1kXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCuxie/btrMacGylJm/ePSjx5uDqMW2afzKeH1kXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCuxie%2FbtrMacGylJm%2FePSjx5uDqMW2afzKeH1kXK%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;1334&quot; height=&quot;634&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료되면? 여기서 찾게되는 인스턴스가 바로 &lt;code&gt;ModelAttributeMethodProcessor&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ModelAttributeMethodProcessor가 처리해주게끔 반환해준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ModelAttributeMethodProcessor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ModelAttribute어노테이션을 읽어들이고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1151&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceYGsX/btrL81eVByk/srljodwKkCtagXFW9Q4MP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceYGsX/btrL81eVByk/srljodwKkCtagXFW9Q4MP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceYGsX/btrL81eVByk/srljodwKkCtagXFW9Q4MP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceYGsX%2FbtrL81eVByk%2FsrljodwKkCtagXFW9Q4MP1%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;1151&quot; height=&quot;525&quot; data-origin-width=&quot;1151&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ServletModelAttributeMethodProcessor가&lt;br /&gt;어노테이션이 붙은 변수의 타입이 url 변수와 일치하는 속성이 있는지&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;850&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HAGA3/btrL9Yhtl1R/2vwcjqsJqlbRaHI7rVpgc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HAGA3/btrL9Yhtl1R/2vwcjqsJqlbRaHI7rVpgc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HAGA3/btrL9Yhtl1R/2vwcjqsJqlbRaHI7rVpgc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHAGA3%2FbtrL9Yhtl1R%2F2vwcjqsJqlbRaHI7rVpgc0%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;850&quot; height=&quot;470&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾고 없으면 null을 반환하여 객체 생성부분으로 간주하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 객체내에 오버로딩된 메소드에서 리플렉션을 이용해서&lt;br /&gt;해당 ModelAttribute 어노테이션이 붙은 객체의 타입을 가져온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;987&quot; data-origin-height=&quot;655&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLXhme/btrL9psQY5k/2BwXGaJdeMd76z9WHFtIm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLXhme/btrL9psQY5k/2BwXGaJdeMd76z9WHFtIm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLXhme/btrL9psQY5k/2BwXGaJdeMd76z9WHFtIm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLXhme%2FbtrL9psQY5k%2F2BwXGaJdeMd76z9WHFtIm0%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;987&quot; height=&quot;655&quot; data-origin-width=&quot;987&quot; data-origin-height=&quot;655&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BeanUtils 클래스를 이용하여&lt;br /&gt;이때 이 타입의 생성자를 찾는데&lt;br /&gt;제일 먼저 기본생성자를 찾고 있다면 기본생성자를 반환하고 아니라면 구현된 생성자를 전부 가져오는데&lt;br /&gt;이때 길이가 1개이면 해당 생성자를 생성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 ModelAttributeMethodProcessor에서 파라미터와 매칭되는 것을 넣어준다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커맨드 객체의 차이?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 생각했을 때 &lt;code&gt;@ModelAttribute&lt;/code&gt;와 &lt;code&gt;@RequestBody&lt;/code&gt;를 읽는 커맨드 객체의 차이점은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RequestBody는 Jackson 라이브러리를 통해서 읽어와 ObjectMapper가 사용되서 조금 생성부분에서 차이가 나지않나 생각한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@RequestBody&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전에 RequestBody 동작이 어떻게 되는지를 보며 작성한 &lt;a href=&quot;https://lsj8367.tistory.com/entry/AbstractMessageConverter&quot;&gt;메세지 컨버터 정리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 부분에 보면 해당 컨버팅 작업을 &lt;code&gt;MappingJackson2HttpMessageConverter&lt;/code&gt;가 수행해주는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;read()&lt;/code&gt; 메소드에서 ObjectMapper를 사용하여 매핑해준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;code&gt;@RequestBody&lt;/code&gt;는 getter메소드와 기본생성자가 있어도 ObjectMapper가 해주기에 setter를 쓰거나 해주지 않아도 주입된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@ModelAttribute&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ModelAttribute는 이와는 다르게 커스텀하게 어노테이션을 만들어서 우리가 &lt;code&gt;MethodArgumentResolver&lt;/code&gt;를 구현해서 파라미터에 값을 넣어주듯,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션 + 리플렉션을 이용해서 값을 넣어주는 것이다. 물론 ServletRequest에서 읽어와서 값들을 가지고 있는건 둘다 공통이지만 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 기본생성자와 getter만 있으면 값을 넣어줄 수가 없기에&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본생성자에 setter를 전부 생성해주거나 전체 필드를 할당할 수 있는 생성자를 만들어 주어야 한다!!&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>Spring</category>
      <category>Java</category>
      <category>Spring</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/124</guid>
      <comments>https://lsj8367.tistory.com/entry/ModelAttribute-RequestBody-%EC%BB%A4%EB%A7%A8%EB%93%9C-%EA%B0%9D%EC%B2%B4#entry124comment</comments>
      <pubDate>Thu, 15 Sep 2022 00:36:35 +0900</pubDate>
    </item>
    <item>
      <title>참조 유형</title>
      <link>https://lsj8367.tistory.com/entry/Java-%EC%B0%B8%EC%A1%B0%EC%9C%A0%ED%98%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이펙티브 자바를 읽다가 약한참조에 대한 이야기가 나와서 포스팅한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조에는 아래 4가지가 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Strong References (강한 참조)&lt;/li&gt;
&lt;li&gt;Soft References (소프트 참조)&lt;/li&gt;
&lt;li&gt;Weak References (약한 참조)&lt;/li&gt;
&lt;li&gt;Phantom References (팬텀 참조)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 해당 참조 유형에 따라 GC 실행 대상여부, 시점이 달라진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;강한참조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new 연산자를 사용하여 객체를 인스턴스화 하고 참조하는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조가 해제되지 않으면 GC의 대상이 되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1662990822543&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Test test = new Test();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 test라는 변수가 참조를 가지고 있다면 GC의 대상이 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test = null이 되는 순간 GC의 대상이 된다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상 객체의 참조가 SoftReference만 있다면 GC의 대상이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, JVM 메모리가 부족한 경우에만 Heap에서 제거된다.&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;pre id=&quot;code_1662992459259&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
        String ss = &quot;문자열&quot;;
        SoftReference&amp;lt;String&amp;gt; reference = new SoftReference&amp;lt;&amp;gt;(ss);

        // 이 시점에 GC의 실행 대상이 가능
        ss = null;

        System.gc();

        // JVM의 메모리가 부족하지 않아서 GC 실행 대상이 되지 않은 경우
        // 그대로 유지한다.
        ss = reference.get();
        System.out.println(ss);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;대상 객체의 참조가 WeakReference만 있다면 GC의 대상이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른점은, 메모리가 부족한경우가 아니라 다음 GC가 일어나게 되면 바로 힙에서 제거된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1662992863288&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
        String ss = &quot;문자열&quot;;
        WeakReference&amp;lt;String&amp;gt; reference = new WeakReference&amp;lt;&amp;gt;(ss);

        // 이 시점에 GC의 실행 대상이 가능
        ss = null;

        System.gc();

        // gc를 명시적으로 호출했지만 컬렉션이 동작하지 않을수도 있음
        // 그래도 무조건 동작한다고 가정
        ss = reference.get();

        // null 로 비어있게 된다.
        System.out.println(ss);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;팬텀참조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성시 ReferenceQueue가 필요하며,&amp;nbsp;PhantomReference의 참조값을 수동으로&amp;nbsp;clear()&amp;nbsp;메서드를 실행해야 하고,&amp;nbsp;PhantomReference.get()&amp;nbsp;메서드는 항상 null을 반환한다는 특징이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-09-12 오후 11.29.41.png&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnZPPo/btrL1zuz3Vk/VQHaQn27SXfSkpc265fBg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnZPPo/btrL1zuz3Vk/VQHaQn27SXfSkpc265fBg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnZPPo/btrL1zuz3Vk/VQHaQn27SXfSkpc265fBg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnZPPo%2FbtrL1zuz3Vk%2FVQHaQn27SXfSkpc265fBg1%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;767&quot; height=&quot;184&quot; data-filename=&quot;스크린샷 2022-09-12 오후 11.29.41.png&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;PhantomReference는 객체 내부의 참조를 null로 설정하지 않고 참조된 객체를 phantomly reachable 객체로 만든 이후에 ReferenceQueue에 enqueue 된다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;자원 정리 (finalizer 보다는 조금 나은 방법) 그렇지만 try-with-resources를 사용하자.&lt;/li&gt;
&lt;li&gt;생성 비용이 비싼 객체가 언제 메모리에서 해제되는지 알 수 있음.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;깃허브 바로가기&quot; href=&quot;https://github.com/lsj8367/laboratory/tree/master/effective-java/src/main/java/com/github/lsj8367/item7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브 바로가기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1662994934093&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - lsj8367/laboratory: 뭔가를 연습해보기 위한 연구 저장소&quot; data-og-description=&quot;뭔가를 연습해보기 위한 연구 저장소. Contribute to lsj8367/laboratory development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/lsj8367/laboratory/tree/master/effective-java/src/main/java/com/github/lsj8367/item7&quot; data-og-url=&quot;https://github.com/lsj8367/laboratory&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmQcAP/hyPLhqeHHF/FHhY9FMqaE4RyYOxsf6YkK/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234&quot;&gt;&lt;a href=&quot;https://github.com/lsj8367/laboratory/tree/master/effective-java/src/main/java/com/github/lsj8367/item7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/lsj8367/laboratory/tree/master/effective-java/src/main/java/com/github/lsj8367/item7&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmQcAP/hyPLhqeHHF/FHhY9FMqaE4RyYOxsf6YkK/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - lsj8367/laboratory: 뭔가를 연습해보기 위한 연구 저장소&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;뭔가를 연습해보기 위한 연구 저장소. Contribute to lsj8367/laboratory development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>effective java</category>
      <category>Java</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/123</guid>
      <comments>https://lsj8367.tistory.com/entry/Java-%EC%B0%B8%EC%A1%B0%EC%9C%A0%ED%98%95#entry123comment</comments>
      <pubDate>Mon, 12 Sep 2022 23:33:01 +0900</pubDate>
    </item>
    <item>
      <title>Checked Exception, Unchecked Exception</title>
      <link>https://lsj8367.tistory.com/entry/Checked-Exception-Unchecked-Exception</link>
      <description>&lt;p&gt;예외를 알아보기 전에&lt;/p&gt;
&lt;p&gt;에러와 예외의 차이부터 알아보도록 하자.&lt;/p&gt;
&lt;h2&gt;에러&lt;/h2&gt;
&lt;p&gt;일단 에러(Error)는 시스템이 비정상적인 상황에 발생하게 된다.&lt;/p&gt;
&lt;p&gt;수습할 수 없는 상황에 놓이게되어 개발자가 예측하지 못한경우이다.&lt;/p&gt;
&lt;h2&gt;예외&lt;/h2&gt;
&lt;p&gt;예외는 개발자가 구현한 로직에서 발생된 실수나 사용자의 영향이 미쳐 발생하게 되는 것이다.&lt;/p&gt;
&lt;p&gt;그렇기에 미리 예측해서 방지할 수가 있다.&lt;/p&gt;
&lt;p&gt;이펙티브 자바를 회독하며 &lt;a href=&quot;https://github.com/back-end-study/effective-java&quot; title=&quot;스터디&quot;&gt;스터디&lt;/a&gt;를 진행하면서 2장에 &lt;/p&gt;
&lt;p&gt;&lt;code&gt;IllegalArgumentException&lt;/code&gt;이 나오게 되어 이 예외 부분을 정리하게 됐다.&lt;/p&gt;
&lt;h3&gt;Checked, Unchecked Exception&lt;/h3&gt;
&lt;p&gt;기본적으로 오류, 예외는 &lt;code&gt;Object&lt;/code&gt;를 상속받는 &lt;code&gt;Throwable&lt;/code&gt;클래스를 상속받아 구현이 되어있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sAwCf/btrLFitO6Pd/knHJejhtLYGy78WKmvZN3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sAwCf/btrLFitO6Pd/knHJejhtLYGy78WKmvZN3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sAwCf/btrLFitO6Pd/knHJejhtLYGy78WKmvZN3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsAwCf%2FbtrLFitO6Pd%2FknHJejhtLYGy78WKmvZN3K%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이미지 출처 - &lt;a href=&quot;https://www.programcreek.com/2009/02/diagram-for-hierarchy-of-exception-classes/&quot;&gt;https://www.programcreek.com/2009/02/diagram-for-hierarchy-of-exception-classes/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;그림을보면 &lt;code&gt;Exception&lt;/code&gt;을 포함해 아래 뿌리로 &lt;strong&gt;빨간색&lt;/strong&gt;으로 표시된 예외들은 모두&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Checked Exception&lt;/code&gt;이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;푸른색&lt;/strong&gt;은 전부 &lt;code&gt;Unchecked Exception&lt;/code&gt;이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RuntimeException&lt;/code&gt;을 상속받는 클래스들이면 하나같이 &lt;code&gt;Unchecked Exception&lt;/code&gt;이라 할 수 있겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by08gb/btrLDGC4em7/ttjem3lgi67VuUuINDxiB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by08gb/btrLDGC4em7/ttjem3lgi67VuUuINDxiB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by08gb/btrLDGC4em7/ttjem3lgi67VuUuINDxiB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby08gb%2FbtrLDGC4em7%2Fttjem3lgi67VuUuINDxiB0%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;보면 알겠지만, 프로그램이 정상 작동중에 실행될 수 있는 예외라는 뜻이다.&lt;/p&gt;
&lt;h3&gt;차이점&lt;/h3&gt;
&lt;p&gt;둘의 큰 차이점은 check, uncheck 둘다 throws로 Exception을 처리하게끔 메소드에 달아주어도&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IO7XK/btrLE1lwAT0/Mk0kVIAJE7LNqBFHTvcW7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IO7XK/btrLE1lwAT0/Mk0kVIAJE7LNqBFHTvcW7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IO7XK/btrLE1lwAT0/Mk0kVIAJE7LNqBFHTvcW7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIO7XK%2FbtrLE1lwAT0%2FMk0kVIAJE7LNqBFHTvcW7k%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;명시적으로 컴파일에서부터 에러를 띄워주는것은 &lt;code&gt;Checked Exception&lt;/code&gt;이고 throws로 던져지는 예외는 모두 반드시 처리해주어야 한다.&lt;br&gt;(상위로 던지거나 자신의 위치에서 try~catch로 처리해주거나)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RuntimeException&lt;/code&gt;하위 예외들도 throws에 넣어줄 수 있겠지만, 처리해주어야 할 필요는 없을 수 있다.&lt;/p&gt;
&lt;p&gt;같이 throws에 넣어주게 되면 이러이러한 예외가 발생한다~ 정도를 나타내는 의미정도로 생각하면 되겠다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;근데 이마저도 너무 많은 예외를 throws에 같이 넣어주면 가독성을 반대로 해칠수도 있다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;예외로 의미있는 어떤 무언가를 반드시 처리해줄 수 있는 로직이라면 &lt;code&gt;Checked Exception&lt;/code&gt;을&lt;/p&gt;
&lt;p&gt;예외 상황이나 문제를 해결할 수 없다면 &lt;code&gt;Unchecked Exception&lt;/code&gt;을 활용해 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Checked Exception&lt;/code&gt;을 던지게 되었을 때 이 예외를 처리하는 무언가의 핸들러까지 던져지게 될텐데 (무지성 throws)&lt;/p&gt;
&lt;p&gt;이럴때 그냥 try~catch해주면서 catch부분에 해당 &lt;code&gt;Checked Exception&lt;/code&gt;을 받아서 &lt;code&gt;Unchecked Exception&lt;/code&gt;으로 바꾸어 던져주는 방법도 있을 것이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;트랜잭션 처리에 관하여...&lt;/h2&gt;
&lt;p&gt;많은 글들에서 트랜잭션 처리 시&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Checked Exception은 Rollback 하지않음&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Unchecked Exception은 Rollback 한다&lt;/code&gt;&lt;br&gt;라고 되어있는데&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;어떤 트랜잭션인지 명시도 되어있지 않고 무작정 Rollback을 한다 안한다의 여부는 조금 잘못되었다고 백기선님이 말씀하시는걸 보았다.&lt;/p&gt;
&lt;p&gt;좀 당연시하게 &lt;code&gt;Spring Framework&lt;/code&gt;에서 &lt;code&gt;@Transactional&lt;/code&gt;이라고 무작정 생각하고 보면 맞다고 느꼈었는데,&lt;/p&gt;
&lt;p&gt;Kafka의 트랜잭션, DB의 트랜잭션 등등 트랜잭션은 정말 많다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecvhxP/btrLFHtmcs7/sivLvVNQnv0GqBlSyec6L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecvhxP/btrLFHtmcs7/sivLvVNQnv0GqBlSyec6L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecvhxP/btrLFHtmcs7/sivLvVNQnv0GqBlSyec6L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FecvhxP%2FbtrLFHtmcs7%2FsivLvVNQnv0GqBlSyec6L0%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이 문서만 잘 읽어봐도 오해라는거다.&lt;/p&gt;
&lt;p&gt;범용적으로 사용하는 &lt;code&gt;Spring Framework&lt;/code&gt;에서 사용되고 있는 &lt;code&gt;@Transactional&lt;/code&gt; 어노테이션의 롤백 기준은&lt;/p&gt;
&lt;p&gt;바로 &lt;code&gt;Unchecked Exception&lt;/code&gt; 종류는 기본 Rollback을 진행한다. 반대로 &lt;code&gt;Checked Exception&lt;/code&gt; 종류는 Rollback을 하지 않는다.&lt;/p&gt;
&lt;p&gt;기본값으로 두개를 나눠서 &lt;code&gt;Check&lt;/code&gt;, &lt;code&gt;Uncheck&lt;/code&gt;에 대해 트랜잭션 전략을 나누고는 있지만,&lt;/p&gt;
&lt;p&gt;어디까지나 &lt;strong&gt;기본값&lt;/strong&gt;이라는거고, &lt;code&gt;Check&lt;/code&gt;, &lt;code&gt;Uncheck&lt;/code&gt;상관없이 언제든 내가 원하면 해당 예외를 통해 Rollback전략을 매겨줄 수 있다는 것이다.&lt;/p&gt;
&lt;p&gt;그러니 &lt;code&gt;Check&lt;/code&gt;와 &lt;code&gt;Uncheck&lt;/code&gt;라고 해서 트랜잭션을 롤백한다 안한다의 이분법적 사고는 잘못된 사고라고 이해를 하게 되었다.&lt;/p&gt;</description>
      <category>Java</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/122</guid>
      <comments>https://lsj8367.tistory.com/entry/Checked-Exception-Unchecked-Exception#entry122comment</comments>
      <pubDate>Wed, 7 Sep 2022 23:40:50 +0900</pubDate>
    </item>
    <item>
      <title>라즈베리파이 사용</title>
      <link>https://lsj8367.tistory.com/entry/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%EC%82%AC%EC%9A%A9%EA%B8%B0</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;시작하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 클라우드 요금은 견딜 수 없어서 상당히 겁이 난 상태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;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;모르는 영단어나 줄여쓰는 영어등등.. 개발 관련 단어들도 뭔가 한 곳에 두고 싶었다.&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-filename=&quot;IMG_3174.JPG&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;1013&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzyuN6/btrK1hvatbM/Y2sB7jXvC5twB6LCMYuH81/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzyuN6/btrK1hvatbM/Y2sB7jXvC5twB6LCMYuH81/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzyuN6/btrK1hvatbM/Y2sB7jXvC5twB6LCMYuH81/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzyuN6%2FbtrK1hvatbM%2FY2sB7jXvC5twB6LCMYuH81%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;509&quot; height=&quot;541&quot; data-filename=&quot;IMG_3174.JPG&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;1013&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아두이노는 한번 써봤는데 이건 처음이라 그래도 OS설치는 이미 되어있다고 해서 받아서 써보는데 되게 귀엽고 성능은 좋은것 같다? (아직 첫날이라 그런걸까....  )&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;아무튼 각설하고 이 서버를 통해서 ssl 적용도 해서 도메인 주소 붙이고 나만의 북마크를 만들어보고 싶어졌다!&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;ㅋㅋㅋㅋ 월급받으면 이 라즈베리파이에 쿨러도 달아줄까 생각중이다. (종일 써야되니 무조건 달아줘야 하는게 아닐까 싶긴 하다)&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;이게 진짜 웃긴게 sd카드는 용량을 다시 한번 확인해봐도 16기가였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-08-30 오후 11.47.39.png&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FoT36/btrKU63sGWm/l1Feb0lCZxOvnZbaqJE3f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FoT36/btrKU63sGWm/l1Feb0lCZxOvnZbaqJE3f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FoT36/btrKU63sGWm/l1Feb0lCZxOvnZbaqJE3f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFoT36%2FbtrKU63sGWm%2Fl1Feb0lCZxOvnZbaqJE3f0%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;472&quot; height=&quot;186&quot; data-filename=&quot;스크린샷 2022-08-30 오후 11.47.39.png&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 전체 디스크 용량을 보면 5.6기가 남짓이다.&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;좀 구글링을 해봤는데, 라즈베리파이에서 제공되는 OS 사용시 메모리가 4기가 남짓만 사용가능하게 설정된다고 한다.&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;크... 87%에서 33%로 감소하는 수준의 용량차이다. ㅋㅋㅋㅋㅋ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-08-30 오후 11.49.47.png&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Or8va/btrKVUPgUI8/lJHY5PAc8l1s7TAkmgC7n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Or8va/btrKVUPgUI8/lJHY5PAc8l1s7TAkmgC7n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Or8va/btrKVUPgUI8/lJHY5PAc8l1s7TAkmgC7n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOr8va%2FbtrKVUPgUI8%2FlJHY5PAc8l1s7TAkmgC7n1%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;447&quot; height=&quot;181&quot; data-filename=&quot;스크린샷 2022-08-30 오후 11.49.47.png&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;181&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;일단 기존에 친동생 방에 이더넷 케이블이 남는 여분이 하나 있어서 당장 쓰지 않는 케이블이라 이더넷으로 연결해서 사용중이다.&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;공유기로 포트 물린게 아니라 따로 회선이 등록되어 있는경우라서 공인 ip로 바로 접속해버렸다.&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;뭐 일단 ssh접속 포트도 바꿨고, public key접속으로만 접속하게끔 허용해놓은 상태라서 일단 당분간은 안심하고 쓸수도 있겠다.&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;뭐 mysql은 일단 설치 잘했고...&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;가장 최신 LTS인 &lt;code&gt;Java17&lt;/code&gt;을 나중에 받아야겠다. 뭔가 homebrew에서 하는 설치랑 조금 다르게 &lt;b&gt;wget&lt;/b&gt;으로 수행해주는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 지금 공부중인 코틀린을 써볼까?! 하는 재밌는 생각이 계속 들면서 기분이 좋아진다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_3146 작게.jpeg&quot; data-origin-width=&quot;309&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBxRva/btrK2mC4us0/k83xhLFGIWWLga86HmfOIk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBxRva/btrK2mC4us0/k83xhLFGIWWLga86HmfOIk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBxRva/btrK2mC4us0/k83xhLFGIWWLga86HmfOIk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBxRva%2FbtrK2mC4us0%2Fk83xhLFGIWWLga86HmfOIk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;309&quot; height=&quot;320&quot; data-filename=&quot;IMG_3146 작게.jpeg&quot; data-origin-width=&quot;309&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스터디도 JVM 관련 스터디와 이펙티브 자바 회독을 하려고 이 스터디도 들어가게 되서 총 2개 스터디를 들어가게 됐다.&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;이것도 같이 병행하면서 열심히 해야겠다. (북마크 만드려면 JavaScript도 공부해야되는데...?)ㅋㅋㅋㅋ&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;아무튼 요근래 못했던 삽질 ATDD과정 끝나면서 이런 값진 행동들을 하면서 많이 에러 맞아볼 수 있는 또 하나의 기회가 오지 않았나 싶다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부할게 정말 많고 시간이 없어서 내 자신을 하나의 프로세스로 두고 여러 스레드를 할당해서 병렬로 지식습득을 하고 싶다  &lt;/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;요즘에 계속 TDD를 하려는 습관을 가지다 보니까 전에 단위 테스트를 작성했던 나보다 훨씬 예외 케이스나 엣지 케이스들을 더 잘 찾아내는것 같다.&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;</description>
      <category>Diary</category>
      <category>diary</category>
      <category>홈서버</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/121</guid>
      <comments>https://lsj8367.tistory.com/entry/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-%EC%82%AC%EC%9A%A9%EA%B8%B0#entry121comment</comments>
      <pubDate>Wed, 31 Aug 2022 00:04:13 +0900</pubDate>
    </item>
    <item>
      <title>운영체제 3강</title>
      <link>https://lsj8367.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-3%EA%B0%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅은 &lt;a title=&quot;반효경 운영체제 강의&quot; href=&quot;http://www.kocw.net/home/search/kemView.do?kemId=1046323&quot;&gt;반효경 운영체제 강의&lt;/a&gt; 를 듣고 정리하는 글이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork, exec실습 코드는 &lt;a href=&quot;https://github.com/lsj8367/process&quot;&gt;깃허브&lt;/a&gt;에 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로세스 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 프로세스는 1개만 존재하고, 자식 프로세스들을 생성한다.&lt;br /&gt;copy on write (cow) 기법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소 공간&lt;br /&gt;자식은 부모의 공간을 복사한다.&lt;br /&gt;자식은 그 공간에 새로운 프로그램을 올린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닉스에서는 fork 시스템 콜 을 통해 부모 프로세스와 똑같이 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수행&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 자식이 공존하며 수행되는 모델&lt;/li&gt;
&lt;li&gt;자식이 종료될 때까지 기다렸다가 수행되는 모델&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 종료&lt;br /&gt;프로세스가 마지막 명령을 수행한 후 운영체제에게 이를 알린다. (exit)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식이 부모에게 output data를 보낸다.&lt;/li&gt;
&lt;li&gt;프로세스의 각종 자원들이 운영체제에게 반납됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 프로세스가 자식의 수행을 종료시킨다. (abort)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식 프로세스가 할당 자원의 한계치를 넘어설때&lt;/li&gt;
&lt;li&gt;자식에게 할당된 태스크가 더 이상 필요하지 않음&lt;/li&gt;
&lt;li&gt;부모가 종료하는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영체제는 부모 프로세스가 종료하는 경우 자식이 더 이상 수행되도록 두지않음&lt;/li&gt;
&lt;li&gt;단계적인 종료&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식을 전부 죽이고서 부모가 죽게되는 구조&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wait()&lt;br /&gt;프로세스가 wait() 시스템 콜을 호출하게 되면,&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널은 child가 종료될 때까지 프로세스 A를 sleep 시킴 (block 상태)&lt;/li&gt;
&lt;li&gt;자식 프로세스가 종료되면 커널은 프로세스 A를 깨운다 (ready 상태)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exit()&lt;br /&gt;프로세스의 종료&lt;br /&gt;exit()을 만나는 순간 바로 종료가 되게 된다.&lt;br /&gt;자바로 하게되면 System.exit(0);&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자발적 종료&lt;br /&gt;마지막 작업 수행 후 exit() 시스템 콜을 통해 수행&lt;br /&gt;프로그램에 명시적으로 넣어주지 않아도 main 함수가 return되는 위치에 컴파일러가 삽입해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비 자발적 종료&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 프로세스가 자식 프로세스를 강제 종료시킴
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식 프로세스가 한계치를 넘어서는 자원 요청&lt;/li&gt;
&lt;li&gt;자식에게 할당된 작업이 더 이상 필요하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;키보드로 kill, break를 수행한 경우&lt;/li&gt;
&lt;li&gt;부모가 종료하는 경우&lt;/li&gt;
&lt;li&gt;부모 프로세스가 종료하기 전에 자식들이 먼저 종료&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로세스간 협력&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;독립적 프로세스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 각자의 주소 공간(코드, 스택, 데이터)을 가지고 수행되므로,&lt;br /&gt;원칙적으로 하나의 프로세스는 다른 프로세스의 수행에 영향을 미치지 못한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;협력 프로세스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 협력 메커니즘을 통해 하나의 프로세스가 다른 프로세스에 영향을 미칠 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로세스 협력 메커니즘 (IPC: Inter Process Communicaiton)&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메세지를 전달하는 방법&lt;br /&gt;message passing : 커널을 통하여 메세지를 전달&lt;br /&gt;프로세스에게 직접 전달하는 방법, mailbox를 통한 전달방법&lt;/li&gt;
&lt;li&gt;주소 공간을 공유하는 방법&lt;br /&gt;shared memory : 서로 다른 프로세스 간에 일부 주소 공간을 공유하게 하는 shared memory 메커니즘이 존재 (이 부분도 커널에게 공유하겠다는 시스템 콜을 이용해야한다)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;program은 CPU를 사용하는 단계 (CPU burst) -&amp;gt; I/O를 수행하는 단계 (IO burst)를 계속 반복하며 실행된다.&lt;br /&gt;(물론 CPU만 사용하는 프로그램도 존재할 수 있음)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러종류의 job(=process)가 섞여있어 CPU스케줄링이 필요하다.&lt;br /&gt;Interactive job에게 적절한 응답 제공하기 위함&lt;br /&gt;CPU와 입출력 장치등의 시스템 자원들을 골고루 효율적으로 사용하기 위함&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 스케줄러&lt;br /&gt;ready 상태의 프로세스 중 이번에 CPU를 줄 프로세스를 고름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dispatcher&lt;br /&gt;CPU의 제어권을 CPU 스케줄러에 의해 선택된 프로세스에게 넘김&lt;br /&gt;이 과정을 context switching(문맥 교환)이라 한다.&lt;/p&gt;</description>
      <category>CS/운영체제</category>
      <category>CS</category>
      <category>운영체제</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/120</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-3%EA%B0%95#entry120comment</comments>
      <pubDate>Mon, 22 Aug 2022 22:31:16 +0900</pubDate>
    </item>
    <item>
      <title>운영체제 2강 - 2</title>
      <link>https://lsj8367.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C2%EA%B0%95-2</link>
      <description>&lt;h1&gt;동기식, 비동기식 입출력&lt;/h1&gt;
&lt;p&gt;프로세스가 입출력이 진행되는 동안에 CPU 점유는 중요하지 않음&lt;/p&gt;
&lt;p&gt;입출력 명령을 계속 대기하면 동기,&lt;br&gt;해놓고서 다른 작업을 수행하면 비동기&lt;/p&gt;
&lt;h4&gt;구현 1&lt;/h4&gt;
&lt;p&gt;I/O가 끝날때 까지 계속 대기해 CPU를 낭비시킴&lt;br&gt;매시점 하나의 입출력만 일어남&lt;/p&gt;
&lt;h4&gt;구현 2&lt;/h4&gt;
&lt;p&gt;입출력이 완료될 때까지 해당 프로그램에서 CPU를 빼앗음&lt;br&gt;다른 프로그램에서 CPU를 가지고 연산 수행&lt;br&gt;입출력 작업이 완료되면 해당 프로세스에 CPU할당&lt;/p&gt;
&lt;h2&gt;스레드&lt;/h2&gt;
&lt;p&gt;프로세스 내부의 CPU 수행단위가 여러개 있는것이 스레드&lt;/p&gt;
&lt;p&gt;프로세스마다 code, data, stack 이 주어짐&lt;/p&gt;
&lt;p&gt;CPU수행과 관련된정보는 스레드가 별도로 가지고있고,&lt;br&gt;나머지는 공유한다.&lt;/p&gt;
&lt;h3&gt;스레드의 구성&lt;/h3&gt;
&lt;p&gt;program counter&lt;br&gt;register set&lt;br&gt;stack&lt;/p&gt;
&lt;h4&gt;스레드의 장점&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;다중스레드 구성된 작업구조에서는 하나의 스레드가 blocked 상태인 경우에도&lt;br&gt;동일한 작업 내의 다른 스레드가 실행되어 빠른 처리가 가능하다.&lt;br&gt;1, 2, 3 을 보여줄때&lt;br&gt;먼저 완료된거 바로 보여주게 되면 사용자에게 좀더 원활한 서비스를 제공가능&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;자원 공유 - 같은 일을 하는 프로그램을 멀티 프로세스를 구성하기 보다는 cpu 수행단위만 여러개로 주게되면 자원을 효율적으로 사용이 가능하다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;같은 작업을 다중 스레드를 사용하여 동시처리가 좋아지고 성능 최적화를 할 수 있다.&lt;br&gt;(병렬 처리)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;여러개의 프로세스에서 각각의 스레드가 서로 다른 CPU에서 병렬적으로 작업 수행이 가능하다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;커널 스레드&lt;br&gt;커널의 지원을 받는 스레드&lt;br&gt;운영체제 커널이 여러 스레드로 구성되어 있는것을 알고있음&lt;/p&gt;
&lt;p&gt;유저 스레드&lt;br&gt;라이브러리를 통해 지원, 프로세스안에 여러 스레드가 있다는것을 운영체제는 모름&lt;br&gt;구현사항에 제약은 있을 수 있음&lt;/p&gt;
&lt;h3&gt;프로세스&lt;/h3&gt;
&lt;p&gt;프로세스는 파일 형태로 존재하는(ex - xxx.exe 등) &lt;strong&gt;프로그램&lt;/strong&gt;이 실행되어 메모리에 올라가 있는 상태를 뜻한다.&lt;/p&gt;
&lt;p&gt;잡(Job)이라는 용어와도 혼용해서 부른다.&lt;/p&gt;
&lt;p&gt;프로세스를 이해하려면 프로세스 문맥(Context)도 알아야 한다.&lt;/p&gt;
&lt;h4&gt;프로세스 문맥(Context)&lt;/h4&gt;
&lt;p&gt;문맥은 해당 프로세스의 주소공간, 레지스터의 값, 시스템 콜 등등을 통해&lt;/p&gt;
&lt;p&gt;커널에서 수행한 작업의 상태, 커널이 관리하고 있는 정보들을 담고있다.&lt;/p&gt;
&lt;p&gt;프로그램이 프로세스가 되면, 운영체제는 프로세스를 관리하기 위한 자료구조를 유지한다.&lt;/p&gt;
&lt;p&gt;이 두개가 바로 &lt;strong&gt;PCB, 커널스택&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;프로세스의 상태에는 실행, 준비, 봉쇄가 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;실행&lt;ul&gt;
&lt;li&gt;프로세스가 CPU를 보유, 기계어 명령을 실행하고 있는 상태&lt;/li&gt;
&lt;li&gt;CPU는 하나뿐이므로, 여러 프로세스가 실행된다 하더라도 실행 상태에 있는 프로세스는 매 시점에 하나뿐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;준비&lt;ul&gt;
&lt;li&gt;CPU를 할당 받지 못한 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;봉쇄&lt;ul&gt;
&lt;li&gt;CPU를 할당받더라도 바로 명령을 실행할 수 없는 프로세스의 상태&lt;/li&gt;
&lt;li&gt;프로세스가 요청한 입출력 작업이 진행중인 경우 이에 해당함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;프로세스의 상태를 구분하는 이유는 컴퓨터의 자원을 효율적으로 관리하기 위함&lt;/p&gt;
&lt;p&gt;크게는 3가지이고&lt;/p&gt;
&lt;p&gt;시작과 완료 상태가 존재한다.&lt;/p&gt;
&lt;p&gt;프로세스가 시작되어 프로세스를 위한 자료구조는 생성됐는데 메모리를 획득하지 못한 경우를 시작상태,&lt;/p&gt;
&lt;p&gt;프로세스와 관련된 자료구조를 완전히 정리하지 못한상태가 완료상태이다.&lt;/p&gt;
&lt;p&gt;운영체제에게 입출력을 요구하건 다른 작업을 요구하면 모두 큐에 줄을 서게되고,&lt;/p&gt;
&lt;p&gt;자기 차례가 되면 그때서야 데이터들을 받게되고, 그 때 인터럽트가 발생해서 입출력 완료 사실을 알림&lt;/p&gt;
&lt;h4&gt;프로세스 제어 블록 (Process Control Block; PCB)&lt;/h4&gt;
&lt;p&gt;PCB는 운영체제가 시스템 내의 프로세스들을 관리하기 위하여 프로세스마다 유지하는 정보들을 저장하는&lt;/p&gt;
&lt;p&gt;커널 내의 자료구조이다. 구성 요소는 아래와 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로세스의 상태&lt;/li&gt;
&lt;li&gt;프로그램 카운터 값&lt;/li&gt;
&lt;li&gt;CPU 레지스터 값&lt;/li&gt;
&lt;li&gt;CPU 스케줄링 정보 (메모리 할당을 위해 필요한 정보)&lt;/li&gt;
&lt;li&gt;메모리 관리 정보 (메모리 할당을 위해 필요한 정보)&lt;/li&gt;
&lt;li&gt;자원 사용 정보&lt;/li&gt;
&lt;li&gt;입출력 상태 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;문맥교환 (Context Switching)&lt;/h4&gt;
&lt;p&gt;하나의 프로세스로부터 다른 프로세스로 CPU 제어권이 이동하는 것을 컨텍스트 스위칭이라 한다.&lt;/p&gt;
&lt;p&gt;교환이 될 때 원래 CPU를 가지고 있던 프로세스는 프로세스의 문맥들을 PCB블록에 저장하고,&lt;/p&gt;
&lt;p&gt;제어권을 받은 CPU는 자기 자신의 저장된 문맥을 PCB로부터 읽어온다. (이 과정이 성능을 느리게 할듯?)&lt;/p&gt;
&lt;p&gt;인터럽트에서 설명을 정리했을 수 있는데,&lt;/p&gt;
&lt;p&gt;프로세스 중간에 입출력을 해야된다거나 등등으로 운영체제를 사용해야 하는 경우에 시스템 콜을 하게 될텐데&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이 때의 프로세스는 컨텍스트 스위칭이 아니라 그냥 해당 프로세스가 실행중인 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그래서 이러한 과정에선 컨텍스트 스위칭이라고 하지 않는다.&lt;/p&gt;
&lt;p&gt;CPU를 점유하는 프로세스가 다른 사용자의 프로세스로 변경되어야만이 컨텍스트 스위칭이라 할 수 있다.&lt;/p&gt;
&lt;p&gt;CPU 할당 시간을 아주 작게 설정하면 너무 많이 컨텍스트 스위칭이 일어나서 오버헤드가 커지며&lt;/p&gt;
&lt;p&gt;성능이 저하될 것이다.&lt;/p&gt;
&lt;p&gt;반대로 너무 시간을 길게 잡게되면 시분할 시스템의 의미가 퇴색될 수 있다.&lt;/p&gt;</description>
      <category>CS/운영체제</category>
      <category>CS</category>
      <category>운영체제</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/119</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C2%EA%B0%95-2#entry119comment</comments>
      <pubDate>Thu, 18 Aug 2022 22:36:46 +0900</pubDate>
    </item>
    <item>
      <title>ATDD, 클린 코드 with Spring 5기 수료 회고</title>
      <link>https://lsj8367.tistory.com/entry/ATDD-%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C-with-Spring-5%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 TDD, Clean Code with Java 12기를 이수하면서,&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;흔히 Spring framework에서 사용하는 Controller, Service Layer 들의 테스트들을 내가 작성할 때에는 전부 Mock을 이용해서&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;이 강의를 신청하게 되었다.&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;h4 data-ke-size=&quot;size20&quot;&gt;1주차 - 인수 테스트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/lsj8367/atdd-subway-map&quot;&gt;https://github.com/lsj8367/atdd-subway-map&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660456727428&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - lsj8367/atdd-subway-map: ATDD 과정 저장소 - 지하철 노선도 관리 미션&quot; data-og-description=&quot;ATDD 과정 저장소 - 지하철 노선도 관리 미션. Contribute to lsj8367/atdd-subway-map development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/lsj8367/atdd-subway-map&quot; data-og-url=&quot;https://github.com/lsj8367/atdd-subway-map&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kbq24/hyPqUBXTvC/5brBCXvMZDXeFkEJwxprS0/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234&quot;&gt;&lt;a href=&quot;https://github.com/lsj8367/atdd-subway-map&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/lsj8367/atdd-subway-map&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kbq24/hyPqUBXTvC/5brBCXvMZDXeFkEJwxprS0/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - lsj8367/atdd-subway-map: ATDD 과정 저장소 - 지하철 노선도 관리 미션&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ATDD 과정 저장소 - 지하철 노선도 관리 미션. Contribute to lsj8367/atdd-subway-map development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2주차 - 인수 테스트와 TDD&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/lsj8367/atdd-subway-path&quot;&gt;https://github.com/lsj8367/atdd-subway-path&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660456724102&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - lsj8367/atdd-subway-path: ATDD 과정 저장소 - 지하철 노선도 경로 찾기 미션&quot; data-og-description=&quot;ATDD 과정 저장소 - 지하철 노선도 경로 찾기 미션. Contribute to lsj8367/atdd-subway-path development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/lsj8367/atdd-subway-path&quot; data-og-url=&quot;https://github.com/lsj8367/atdd-subway-path&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/o1hTJ/hyPqUPvygT/jIPcKiZQsQKpsBdXpwoI70/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234&quot;&gt;&lt;a href=&quot;https://github.com/lsj8367/atdd-subway-path&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/lsj8367/atdd-subway-path&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/o1hTJ/hyPqUPvygT/jIPcKiZQsQKpsBdXpwoI70/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - lsj8367/atdd-subway-path: ATDD 과정 저장소 - 지하철 노선도 경로 찾기 미션&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ATDD 과정 저장소 - 지하철 노선도 경로 찾기 미션. Contribute to lsj8367/atdd-subway-path development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3주차 - 인수 테스트와 인증&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/lsj8367/atdd-subway-favorite&quot;&gt;https://github.com/lsj8367/atdd-subway-favorite&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660456720000&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - lsj8367/atdd-subway-favorite: ATDD 과정 저장소 - 인증 기반 인수 테스트 미션&quot; data-og-description=&quot;ATDD 과정 저장소 - 인증 기반 인수 테스트 미션. Contribute to lsj8367/atdd-subway-favorite development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/lsj8367/atdd-subway-favorite&quot; data-og-url=&quot;https://github.com/lsj8367/atdd-subway-favorite&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/D1RlB/hyPqWGyNm7/E5ZrJBuVG0Cm3h4KLridD1/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234&quot;&gt;&lt;a href=&quot;https://github.com/lsj8367/atdd-subway-favorite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/lsj8367/atdd-subway-favorite&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/D1RlB/hyPqWGyNm7/E5ZrJBuVG0Cm3h4KLridD1/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - lsj8367/atdd-subway-favorite: ATDD 과정 저장소 - 인증 기반 인수 테스트 미션&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ATDD 과정 저장소 - 인증 기반 인수 테스트 미션. Contribute to lsj8367/atdd-subway-favorite development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4주차 - 인수 테스트와 리팩토링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/lsj8367/atdd-subway-fare&quot;&gt;https://github.com/lsj8367/atdd-subway-fare&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660456707155&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - lsj8367/atdd-subway-fare: ATDD 과정 저장소 - 테스트 기반 문서화 미션&quot; data-og-description=&quot;ATDD 과정 저장소 - 테스트 기반 문서화 미션. Contribute to lsj8367/atdd-subway-fare development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/lsj8367/atdd-subway-fare&quot; data-og-url=&quot;https://github.com/lsj8367/atdd-subway-fare&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhd0UG/hyPq3Tdu7O/o4w38lzSLkSkfZXkK9BBkk/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234&quot;&gt;&lt;a href=&quot;https://github.com/lsj8367/atdd-subway-fare&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/lsj8367/atdd-subway-fare&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhd0UG/hyPq3Tdu7O/o4w38lzSLkSkfZXkK9BBkk/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_144_1057_234');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - lsj8367/atdd-subway-fare: ATDD 과정 저장소 - 테스트 기반 문서화 미션&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ATDD 과정 저장소 - 테스트 기반 문서화 미션. Contribute to lsj8367/atdd-subway-fare development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 4주까지 피드백 받으면서 인수 테스트를 작성했더니 시나리오 흐름대로 뭔가 이해할 수 있게 테스트를 구성하는게 가능해진것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 한번 사석에서 리뷰를 받아보니까 어떤 부분을 내가 놓치고 있었고, 어떤 부분에서 조금 더 나은 선택을 할 수 있는지 많은 고민을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할 수 있어서 굉장히 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestAssured를 사용해서 인수테스트를 진행했는데, MockMvc와의 다른점과 좋았던 점 등등을 다른 포스팅에서 다시 쓰도록 하겠다.&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;한 프로젝트를 돌아가게 작성만 해놓고 다른 프로젝트를 또 시작하는 SI 형식의 개발이 아니기 때문에,&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;다만 이 과정중에 아쉬웠던 것은, 조금 더 여유있을 때 해볼걸 하는 생각도 있지만 끝나고 보니 열심히 하는 다른 분들이 있어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같이 자극받고 하지 않았나 싶다!!&lt;br /&gt;&lt;br /&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;아무튼 좋았고 되게 값진 경험이었다!&lt;/p&gt;</description>
      <category>Diary</category>
      <category>회고</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/118</guid>
      <comments>https://lsj8367.tistory.com/entry/ATDD-%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C-with-Spring-5%EA%B8%B0-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0#entry118comment</comments>
      <pubDate>Sun, 14 Aug 2022 15:03:19 +0900</pubDate>
    </item>
    <item>
      <title>블로그를 옮기고 최신 근황</title>
      <link>https://lsj8367.tistory.com/entry/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%98%AE%EA%B8%B0%EA%B3%A0-%EC%B5%9C%EC%8B%A0-%EA%B7%BC%ED%99%A9</link>
      <description>&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;드디어!! 진짜 날짜에 맞는 첫글을 써본다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 전 직장에서 6개월을 하고 그만 둬버렸다. 뭔가 시도해보고 도입해보려고 했던건 많이 도입을 해보았던 것 같다.&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;b&gt;그래서 이직을 하게 됐다!!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 근속기간이 짧고 그렇게 이르게 이직을 했다는 것 자체가 문제라고 본다면 문제일 수 있다고 생각하지만,&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;B2C 서비스를 하던 기존 회사였지만, 그래도 아직 시장이 되게 작았었고,&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;B2B 핀테크 서비스로 옮기면서 전 회사가 작았던 탓인지 지금 이곳이 트래픽이 훨씬 많다!!!&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달이 조금 넘은 지금은 CTO님이 계셔서 탄탄하게 흘러가고 있다는 것이 좋게 느껴진다.&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;h4 data-ke-size=&quot;size20&quot;&gt;아키텍처 리뷰&lt;/h4&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;서비스가 점점 커지며 MSA 아키텍처로 전환하면서 지금의 회사는 Kafka를 도입했다. (이 포스팅에 대해선 추후에 다시 다루도록 하겠다.  )&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;처음 써보는 Kafka지만 그래도 다들 많이들 도와주셔서 잘 구축한거라고 생각한다.&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;코드리뷰도 적극적으로 한 PR에 코드 수정이 아니더라도 생각할 수 있게끔 주시는 코멘트도 많아서&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;답변까지 포함해서 첫 PR기준으로 4~50개가 달렸었다. 각기 다른 시각으로 봐주는게 내 입장에서는 정말 좋다고 느꼈다.&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;/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;VOC도 처리하면서 온보딩 받아가며 하다보니 잘 잡혀가는 것 같다.&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;그래도 계속 내가 성장하는데 발목을 잡는건 CS였다. 뭔가 잘 개발하는것 같다가도 궁금증이 생기는 건 무조건 CS였다.&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;그래서 최근에 내가 거주하는 지역에서 오프라인 모각코를 모집했다.&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;a href=&quot;https://www.inflearn.com/studies/616442&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스터디 구인 글&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660401023082&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;부천 일요일 모각코 모집합니다. - 인프런 | 스터디&quot; data-og-description=&quot;스터디 주제 : 모각코 예상 스터디 일정(횟수) : 2개월간 주 1회(일요일) 안녕하세요 부천 일요일 모각코를 모집합니다! 시간은 10:00 ~ 13:00 이며, 현재 인원은 6명으로 구성이 되어있고, 추가적으로&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/studies/616442&quot; data-og-url=&quot;https://www.inflearn.com/studies/616442&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Zp7FS/hyPpJaD4TP/tRtcGB58WwRFwZHK1dKssk/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=751_416_794_464,https://scrap.kakaocdn.net/dn/QYreE/hyPq6aZhBM/K4uVYo8XrsVU8zbjr8JcLK/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=751_416_794_464&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/studies/616442&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/studies/616442&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Zp7FS/hyPpJaD4TP/tRtcGB58WwRFwZHK1dKssk/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=751_416_794_464,https://scrap.kakaocdn.net/dn/QYreE/hyPq6aZhBM/K4uVYo8XrsVU8zbjr8JcLK/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=751_416_794_464');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;부천 일요일 모각코 모집합니다. - 인프런 | 스터디&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;스터디 주제 : 모각코 예상 스터디 일정(횟수) : 2개월간 주 1회(일요일) 안녕하세요 부천 일요일 모각코를 모집합니다! 시간은 10:00 ~ 13:00 이며, 현재 인원은 6명으로 구성이 되어있고, 추가적으로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 모집하면서 나를 포함한 8명이 내일이면 처음 모각코를 시작한다.&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;계속 생각하는 것이지만 커머스, 금융 도메인에서 뭔가 돈이 관련된 서비스들을 주로 다루어보고 싶었다.&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;이제는 지금 회사에서 &lt;b&gt;꾸준히&lt;/b&gt; 잘 성장하다보면 언젠가는 내가 원하는 회사에 갈 수 있지 않을까 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 회고에서는 지금보다 더 성장하는 내가 되었으면 하고 계속 반복적인 연습이 답인것 같다.  &lt;/p&gt;</description>
      <category>Diary</category>
      <category>diary</category>
      <category>회고</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/117</guid>
      <comments>https://lsj8367.tistory.com/entry/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%98%AE%EA%B8%B0%EA%B3%A0-%EC%B5%9C%EC%8B%A0-%EA%B7%BC%ED%99%A9#entry117comment</comments>
      <pubDate>Sat, 13 Aug 2022 23:40:33 +0900</pubDate>
    </item>
    <item>
      <title>운영체제 2강 - 1</title>
      <link>https://lsj8367.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-2%EA%B0%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;반효경 운영체제 강의 정리&lt;/p&gt;
&lt;h1&gt;컴퓨터 시스템 2&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 프로그램이 운영체제를 통해서 무언가를 해야할 때에는 시스템 콜을 이용한다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로그램 카운터 (PC, Program Counter)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 카운터는 CPU안에 포함되어 있는데,&lt;br /&gt;이는, 메모리에서 다음 실행되어야할 작업의 주소가 들어있다.&lt;br /&gt;CPU는 이 주소를 바탕으로 다음 작업을 찾아가 작업을 수행하게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터럽트가 발생한다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터럽트가 발생한 경우에는, 프로그램 카운터가 가리키고 있는 주소로 바로 이동하지 않고,&lt;br /&gt;CPU제어권이 OS로 가게 되면서 해당 인터럽트를 우선으로 처리해준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동기식, 비동기식 입출력&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/8eede5fc-0de7-4441-93cb-57300e809dbe/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교적 최근에 내가 29cm의 지원을 했을때를 떠올려본다.&lt;br /&gt;그때 당시, 자바에서 csv파일을 읽어들여야 하는 작업이 있었다.&lt;br /&gt;그 부분을 토대로 이해해보면&lt;br /&gt;과제에서 csv를 읽어들이는 로직이 필요했는데, 자바에서 다른 작업을 OS에서 메모리를 할당받고, CPU를 할당받게 되면서 잘 진행하다가 파일을 읽어들이는 I/O 관련 작업을 실행하려고 할 때 디스크로 부터 가져와야 하니 커널모드의 운영체제가 필요했을 것이다.&lt;br /&gt;근데 이 맥락에서 자바 프로세스는 CSV파일을 읽어들이는 I/O관련 로직은 사용자 프로그램에서는 진행할 수 없기에 시스템콜을 위한 인터럽트를 발생시킨다.&lt;br /&gt;이렇게 발생되면 CPU는 인터럽트를 감지하여 주도권을 OS에게 넘기고,&lt;br /&gt;OS는 하드디스크를 관리하는 디바이스 컨트롤러에게 위임하여 csv파일을 읽도록 위임하면서, 동기식이라면 디바이스 컨트롤러가 로컬 버퍼에 적재하는 작업을 모두 완료&lt;br /&gt;할때까지 대기한다.&lt;br /&gt;디바이스 컨트롤러가 이작업을 완료하는데까지 CPU는 대기상태에 들어가게 될 것이다.&lt;br /&gt;그리고선 디바이스 컨트롤러가 완료했다는 인터럽트를 발생시키면&lt;br /&gt;CPU는 다시 자바 프로세스에 할당이 된다음 다음 작업을 처리할 것이다. &lt;br /&gt;다시 할당되는 과정을 질문을 해보았는데,&lt;br /&gt;cpu 디스패처로 할당하는것 같다고 답변을 들었다. (이부분은 좀 더 뒤에서 다뤄봐야할듯?)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동기식 입출력&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;I/O 입출력 작업이 완료되어야 다른 작업 진행 (완료되기 전까지 대기)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 완료되기전 까지 대기한다면 CPU가 낭비된다.&lt;br /&gt;매 시점에 하나의 입출력만 가능하게 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;I/O 입출력 요청 후 다른 프로세스에게 CPU를 토스한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넘겨받은 CPU도 I/O를 요청하여 계속 다음 프로세스에게 CPU를 토스할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비동기식 입출력&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기식은 I/O입출력을 요청한 후에&lt;br /&gt;바로 CPU 제어권을 얻어서 다른 작업들을 쭉쭉 수행하다가&lt;br /&gt;입출력 작업이 완료되면 인터럽트를 발생시켜줘서 완료 시점을 캐치하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두가지 모두 인터럽트를 통하여 알려준다는 사실은 동일하다!!!&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Memory Mapped I/O&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 입출력은 메모리에 접근하는 명령과 I/O 장치를 접근해야 하는 명령이 구분되어 있다.&lt;br /&gt;Memory Mapped I/O는 I/O 장치에 대해 메모리 주소를 할당해놓은것이다.&lt;br /&gt;주소로 호출을 하게 되면 그것이 I/O를 하게 되는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;저장장치 계층 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레지스터 -&amp;gt; 캐시 메모리(SRAM) -&amp;gt; 메인 메모리(DRAM) -&amp;gt;&lt;br /&gt;(CPU가 여기서부터 직접 접근 불가) 하드 디스크 -&amp;gt; optical disk -&amp;gt; magnetic tape&lt;br /&gt;이 순서의 계층 구조로 되어있으며, 오른쪽으로 가면 갈수록 비용이 싸면서, 용량 증가하고 속도는 느려진다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/2f37f668-0796-4685-9686-8a5bb8b9b81a/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 출처 - &lt;a href=&quot;http://www.kocw.net/home/search/kemView.do?kemId=1046323&quot;&gt;http://www.kocw.net/home/search/kemView.do?kemId=1046323&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 프로그램을 실행시키면 메모리의 프로세스로 올라가는데&lt;br /&gt;중간단계를 거치는 것이 가상 메모리&lt;br /&gt;A라는 프로그램의 주소공간 0번지부터 시작하는것이 생기게 된다. (각 프로그램마다)&lt;br /&gt;stack, data, code를 가지고 있음&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;code
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터럽트, 시스템콜 처리 코드&lt;/li&gt;
&lt;li&gt;자원 관리 위한 코드&lt;/li&gt;
&lt;li&gt;서비스 제공을 위한 코드&lt;/li&gt;
&lt;li&gt;쉽게말해 CPU와 소통할 커널부분의 코드들이 적재됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;data
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pcb, cpu, memory, disk 와 소통 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;stack
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스 마다의 스택이 생성됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종료시키면 가상 메모리가 사라짐&lt;br /&gt;통째로 메모리에 올리면 메모리가 낭비되기 때문에&lt;br /&gt;현재 실행되는것만 메모리에서 사용했다가 더이상 사용하지 않으면 제거&lt;br /&gt;커널은 부팅하고나서 띄워지면 항상 상주&lt;br /&gt;결국 프로그램은 커널모드, 유저모드를 반복하다가 끝나게 되는것&lt;/p&gt;</description>
      <category>CS/운영체제</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/116</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-2%EA%B0%95#entry116comment</comments>
      <pubDate>Thu, 11 Aug 2022 07:30:55 +0900</pubDate>
    </item>
    <item>
      <title>운영체제 1강</title>
      <link>https://lsj8367.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-1%EA%B0%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;반효경 교수님의 운영체제 강의를 정리하는 포스팅&lt;br&gt;System Structure &amp;amp; Program Execution 1&lt;/p&gt;
&lt;h1&gt;  컴퓨터 시스템 구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 시스템의 구조는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/6efbe8bf-f39f-4684-959f-d1dcae0b3cd8/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;이미지 출처 - &lt;a href=&quot;https://asfirstalways.tistory.com/115&quot;&gt;https://asfirstalways.tistory.com/115&lt;/a&gt;&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 시스템에는 크게 중앙 처리장치인 CPU, 메모리, 그리고 외부 장치들인 디스크, 키보드 등등&lt;br&gt;으로 구분된다.&lt;br&gt;컴퓨터는 외부에서부터 데이터를 읽어와 연산을 한 후에 다시 출력해주는 방식으로 처리한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  CPU&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CPU&lt;/code&gt;는 &lt;code&gt;Memory&lt;/code&gt;에 올라간 프로그램들의 명령들을 하나하나 읽어들여 수행하는 역할을 담당한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;I/O&lt;/code&gt;마저도 이 &lt;code&gt;CPU&lt;/code&gt;가 관리하게 되면, 너무나도 많은 인터럽트가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우에는 CPU가 효율적이지 못하다고 할 수 있다. (오버헤드가 너무 큼)&lt;br&gt;그래서 이것을 방지하게 나오는 것이 바로 DMA(Direct Memory Access) Controller를 사용한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;접근 범위&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU가 접근 가능한 곳은 메모리와 Local Buffer이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Interrupt Line&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 자기가 처리하던 연산 중간에 인터럽트가 발생하게 되면, 하던일을 두고&lt;br&gt;인터럽트에 관련된 업무를 먼저 처리한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Mode Bit&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
 &lt;li&gt;커널모드&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두가지 모드가 존재하는 이유는, I/O장치들을 보호하기 위해서이다.&lt;br&gt;모든걸 조작해서 악의적인 프로그램을 만들어서 I/O 장치에 접근할 수 없게하고, 운영체제를 통해서만&lt;br&gt;I/O를 수행할 수 있게 하는것.&lt;/p&gt;
&lt;ul&gt; 
 &lt;li&gt;유저모드 
  &lt;ul&gt; 
   &lt;li&gt;어플리케이션이 실행되는 영역&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;커널모드 
  &lt;ul&gt; 
   &lt;li&gt;프로그램들이 잘 수행되다가 인터럽트가 발생되어 운영체제가 호출되어 수행되는 영역&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 사용자 모드에서 작업을 수행하다 중요한 작업을 수행해야 할 경우에는 System Call(소프트웨어 인터럽트)을 통해 운영체제에게 서비스를 대신해 줄 것을 요청하게 된다.&lt;br&gt;그러면 CPU의 제어권은 다시 운영체제로 넘어가게 되고 인터럽트가 발생할 때에는 모드 비트가 자동적으로 0(커널모드)으로 세팅되어 필요한 작업을 수행하고 요청된 작업이 끝나게 되면 모드 비트는 다시 1(유저모드)로 만들어 사용자 프로그램에게 CPU를 넘겨주게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  DMA (Direct Memory Access) Controller&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DMA 컨트롤러는 Local Buffer에 저장된 데이터들을 메모리로 복사하는 작업이 완료 되면,&lt;br&gt;그때만 CPU에게 인터럽트를 발생시킨다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Memory Controller&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 컨트롤러는 현재 위의 구조대로면, CPU와 DMA 컨트롤러가 서로 메모리에 접근이 가능하다.&lt;/p&gt;
&lt;p&gt;그래서 만약 &lt;code&gt;CPU&lt;/code&gt;, &lt;code&gt;DMA&lt;/code&gt;가 동시에 접근하는 경우 데이터의 일관성이 깨질 수 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로의 사용을 분배해주는게 바로 이 메모리 컨트롤러이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Device Controller&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 컨트롤러는 해당 I/O를 관리하는 작은 CPU개념이다.&lt;br&gt;제어 정보를 위해 control, status register를 가진다.&lt;br&gt;Local Buffer(실제 데이터 저장)를 가진다.&lt;br&gt;I/O는 Device와 Local Buffer 사이에서 일어난다.&lt;br&gt;I/O가 끝난 경우에는 CPU에게 인터럽트로 알리게 된다. (DMA가 존재하면 DMA Controller)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Local Buffer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바이스 컨트롤러가 데이터를 임시로 저장하기 위한 작업 공간.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Device Driver&lt;/strong&gt;도 있는데 이는 CPU가 실행하는 각 디바이스들에 접근하기 위한 소프트웨어이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Timer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한 루프문을 돌게되는 어떤 프로그램만 CPU를 독점하는 상황이 생길 수 있다.&lt;br&gt;이럴 때를 대비해서 만든 것이 바로 타이머이다.&lt;br&gt;타이머는 특정 프로그램이 CPU를 독점하는 것을 막아주는 역할을 수행한다.&lt;br&gt;컴퓨터를 처음 시작하면 운영 체제가 CPU를 가지고 있다가 사용자 프로그램에게 CPU를 넘겨준다.&lt;br&gt;이 때, 그냥 넘겨 주지 않고 타이머 값을 설정하고 넘겨준다.&lt;br&gt;어떤 프로그램이 설정된 Timer의 값이 0이 되었을 때 타이머 인터럽트가 발생하여 다른 프로그램에게 CPU를 넘겨준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Interrupt&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU가 한개의 작업밖에 수행할 수 없는데,&lt;br&gt;하나의 작업을 수행중에 I/O가 발생하거나, 다른 우선 순위가 급한일이 생기게 되면 이 인터럽트가 발생된다.&lt;br&gt;키보드에서 'a'라는 키를 누르게 되면, 이 키의 코드값이 Local Buffer에 저장되고 인터럽트가 발생해서&lt;br&gt;처리하고 있던 작업을 인터럽트가 발생하기 직전까지의 정보를 저장(여기가 바로 PCB) 하는 인터럽트 처리 루틴을 수행한다.&lt;br&gt;인터럽트는 소프트웨어, 하드웨어 인터럽트 2개가 존재한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소프트웨어 인터럽트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌다가 운영체제에게 대신 해달라고 요청할 경우 인터럽트를 발생시킬 수 있음&lt;br&gt;종류 - 예외 상황, System Call&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;하드웨어 인터럽트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어가 발생시키는 인터럽트로, CPU가 아닌 다른 하드웨어 장치가 cpu에 어떤 사실을 알려주거나 cpu 서비스를 요청해야 할 경우 발생시킨다.&lt;br&gt;ex - I/O완료 인터럽트 발생 (하드웨어 인터럽트)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;인터럽트 벡터&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터럽트 처리 루틴 주소를 알고 있다. 종류마다 그 인터럽트가 발생하면 어디있는 함수를 실행하는지&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;인터럽트 처리 루틴&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터럽트 처리 루틴을 통해 해당하는 인터럽트 처리를 완료하고 나면 원래 수행하던 작업으로 돌아갈 위치를 알아야 하고, &lt;br&gt;인터럽트 처리 전에 수행 중이던 작업이 무엇이었는지 반드시 저장해야 한다.&lt;br&gt;그래서 운영 체제는 PCB라는 공간을 별도로 가지고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  System call&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 프로그램이 운영체제의 서비스를 받기 위해 커널 함수를 호출&lt;br&gt;모든 입출력 명령은 운영 체제만 사용할 수 있는 특권 명령으로만 가능하다. 그래서 사용자 프로그램은 이 시스템 콜을 활용한다.&lt;/p&gt;</description>
      <category>CS/운영체제</category>
      <category>CS</category>
      <category>운영체제</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/115</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-1%EA%B0%95#entry115comment</comments>
      <pubDate>Thu, 11 Aug 2022 07:30:41 +0900</pubDate>
    </item>
    <item>
      <title>변성</title>
      <link>https://lsj8367.tistory.com/entry/%EB%B3%80%EC%84%B1</link>
      <description>&lt;h1&gt;자바 변성 (Variance)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 가변성에는 크게 공변, 무공변, 반공변이 존재한다.&lt;br&gt;제네릭을 잘 사용하려면 이 가변성에 대한 이해가 필요하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;변성을 제대로 이해하려면 &quot;타입 S가 T의 하위 타입일 때, Box[S]가 Box[T]의 하위 타입인가?&quot; 라는 질문에서 시작하는게 좋다.&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 공변, 제네릭은 무공변이 기본이라고 다들 알고 있을 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무공변 (Invariance) or 불공변&lt;/h3&gt;
&lt;p&gt;기본적으로 제네릭은 &lt;strong&gt;무공변&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무공변이라고 하니 헷갈리는것 같다. 사전적으로 번역해보면 불공변으로 나오게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;타입 S가 T의 하위 타입일 때, Box[S]와 Box[T] 사이에 상속 관계가 없는 것&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 너는너, 나는 나 인 느낌이다.&lt;br&gt;그래서 선언한 유형만 들어갈 수 있게 코드를 구성할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Object&lt;/code&gt;에는 &lt;code&gt;Object&lt;/code&gt;만, &lt;code&gt;String&lt;/code&gt;에는 &lt;code&gt;String&lt;/code&gt;만 들어갈 수 있단 얘기이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;void invariance() {
    // 제네릭은 기본적으로 무공변
    List&amp;lt;Object&amp;gt; objectList = new ArrayList&amp;lt;&amp;gt;();
    List&amp;lt;String&amp;gt; stringList = new ArrayList&amp;lt;&amp;gt;();

    objectList.add(1);
    objectList.add(1.0);

    stringList.add(&quot;aaaaa&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공변&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;공변(covariance)는 타입 S가 T의 하위 타입일 때, Box[S]가 Box[T]의 하위 타입 임을 나타내는 개념&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Test
void arrayTest() {
    Object[] arr = new Long[5]; //배열에서는 공변이고, Long은 Object의 하위타입이기에 할당이 가능하다.
    arr[0] = &quot;arr&quot;; //공변으로 인해 선언한 arr은 Object로 참조가 된상태라 String도 할당 가능.
    // 여기서 런타임에 ArrayStoreException 발생
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 이 배열을 공변으로 열어두지 않았다면, 다형성의 이점을 살릴 수 없게 됐을 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;Arrays.swap()&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/eb31c203-0a48-42df-b8f8-5f7045ddb902/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Arrays의 메소드를 하나를 가져와봤는데,&lt;br&gt;만약 공변이 아니었다면, 이 배열 스왑 메소드는 객체별로 전부 구현해주어야 했을 것이다.&lt;br&gt;제네릭이 있기전엔 형변환에 대한 에러가 나더라도, &lt;br&gt;다형성의 장점으로 얻을 수 있는 이득이 많았을 것 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;리스트의 공변&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;void variance() {
    List&amp;lt;? extends Object&amp;gt; list = new ArrayList&amp;lt;Number&amp;gt;();
    list.add(1); //컴파일 에러
    list.add(1.0); //컴파일 에러
    list.get(0); // 정상 로직
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 선언을 했을때 add는 선언된 제네릭으로 변수를 넣게 되어있는데,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;무공변으로 만들었을 경우&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/0b595831-937c-4df2-a3aa-28a8cc810ecf/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;공변인 경우&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/23dc61e3-5652-4d42-9127-1b7d9d92520d/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;위와 같은 경우에는 &lt;code&gt;capture of ? extends Object e&lt;/code&gt; Object의 하위타입은 맞지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 타입인지는 모른다? 라는 뜻이라고 생각된다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;code&gt;list.get(0)&lt;/code&gt;이 Object로 형변환은 가능하지만, 반대로 &lt;code&gt;add()&lt;/code&gt;를 통해 null을 제외한 무언가를 추가해줄 수는 없다는 소리이다. 안에 들어가는 객체가 정확하게 뭔지 모르기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 정확한 타입이 어떤건지는 모르기 때문에 개발자가 null을 제외하고는 아무것도 추가하지 못하게 막을 수 있다라고 봐도 될 것 같다.&lt;br&gt;그래서 자주쓰던 Collections 클래스의 UnmodifiableList를 찾아보게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/fd1c39e1-9ebd-4335-b5d3-e6cda6a740b0/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자에 이런식으로 공변을 이용해서 막아주고 있는것을 볼 수 있었다.&lt;br&gt;그러면서 List의 구현체이기 때문에 밖에서는 add에 어떤 값을 넣어줄 수는 있기에, 그대로&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Override&lt;/code&gt;로 재정의 한 뒤에 Exception을 던져주게 만든것을 확인할 수 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반공변(Contravariance)&lt;/h3&gt;
&lt;p&gt;반공변 처음 봤을때 반만 된다 이런생각을 했었다.&lt;del&gt;ㅋㅋㅋㅋㅋㅋㅋ&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그게 아니라 공변의 반대&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;타입 S가 T의 하위 타입일 때, Box[S]가 Box[T]의 상위 타입 임을 나타내는 개념입니다.&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Test
void contravariance() {
    List&amp;lt;? super Number&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
    list.add(1.0);

    final Number number = (Number) list.get(0);
    final Object object = list.get(0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Number&lt;/code&gt;를 포함한 &lt;code&gt;Number&lt;/code&gt;의 상위 타입들만 들어갈 수 있게 설정한 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까는 하위타입이 뭔지 알 수가 없다는 것이었는데,&lt;/p&gt;
&lt;p&gt;이 코드는 &lt;code&gt;Number&lt;/code&gt; 상위인건 알겠는데 상위 누구인지를 알 수 없는 상태이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;super&lt;/code&gt;키워드 다음에 붙은 클래스까지의 형은 전부 넣을 수 있다는 소리와도 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시말하면, 최소 Number 타입은 보장이 된다는 소리와 같다.&lt;br&gt;그래서 list.get(0); 에서 최상 타입인 Object로 꺼내서&lt;/p&gt;
&lt;p&gt;형에 맞는 캐스팅 or &lt;code&gt;instanceof&lt;/code&gt;를 통해 값을 읽어오는게 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 자바의 가변성에 대해 알아보았다.&lt;br&gt;얼추 정리되면서 감은 잡은것 같다.&lt;br&gt;PECS(Producer Extends Consumer Super)를 보면서,&lt;br&gt;일반적으로 소비(Consume)라는게 스타크래프트의 디파일러가 저글링을 컨슘해서 저글링을 잡아먹기때문에,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;스타크래프트의 컨슘&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/50164f42-7165-4def-ad2a-6c09aa4dfa7a/image.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 컬렉션이 만들어지는 과정이 컨슘이라고 생각하고 값을 빼내는 과정(get)이 동작한다고 알고 있었다.&lt;br&gt;반대로 생산자(Producer)는 말그대로 생산이기에 값을 생성해주는(new) or 더해주는(add) 것이 생산자로 알고 있었다.&lt;br&gt;반대로 알고있던 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;올바른 내용&lt;/h3&gt;
&lt;p&gt;컬렉션을 뒤져서 어떤 작업들을 처리 해주어야 한다면 그게 바로 컬렉션 값을 빼내(get) 뭔가를 만들기 때문에 생산자가 되어 &lt;code&gt;extends&lt;/code&gt;를 사용해야 한다는 것이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션에 값을 추가해야되면 매개변수로 주어진 값이 소비되어 컬렉션에 들어가니(add) 소비자 관점이라고 보는것 같다.&lt;/p&gt;
&lt;p&gt;그래서 이 경우에는 &lt;code&gt;super&lt;/code&gt;를 사용해주면 되겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;휴..되게 어렵다  &lt;/p&gt;
&lt;p&gt;아무튼 읽기전용으로 만들고 싶을때에는 &lt;code&gt;extends&lt;/code&gt;를 사용하는것.&lt;/p&gt;
&lt;p&gt;좀더 안전하게 데이터 삽입을 하고싶다면 &lt;code&gt;super&lt;/code&gt;를 사용하는 것만 기억하면 될 것 같다.&lt;/p&gt;</description>
      <category>Java</category>
      <category>Java</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/114</guid>
      <comments>https://lsj8367.tistory.com/entry/%EB%B3%80%EC%84%B1#entry114comment</comments>
      <pubDate>Thu, 11 Aug 2022 07:30:30 +0900</pubDate>
    </item>
    <item>
      <title>Jenkins 에러</title>
      <link>https://lsj8367.tistory.com/entry/Jenkins-%EC%97%90%EB%9F%AC</link>
      <description>&lt;h1&gt;Jenkins&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위키백과에서 발췌한 내용에 따른다.&lt;br&gt;젠킨스(Jenkins)는 소프트웨어 개발 시 지속적 통합(continuous integration) 서비스를 제공하는 툴이다. 다수의 개발자들이 하나의 프로그램을 개발할 때 버전 충돌을 방지하기 위해 각자 작업한 내용을 공유 영역에 있는 Git등의 저장소에 빈번히 업로드함으로써 지속적 통합이 가능하도록 해 준다. MIT 라이선스를 따른다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;발생 시점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재의 회사에서 배포를 젠킨스를 이용하여 배포를 진행한다.&lt;br&gt;신규 기능개발과 레거시를 청산하는 작업을 주로 해왔었어서 이쪽을 고치는게 우선은 아니었다.&lt;br&gt;그래서 모르고 있었던 것일수 있다.&lt;br&gt;에러 상황을 확인해보자&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에러 상세&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[Pipeline] End of Pipeline
java.lang.InterruptedException
    at java.base/java.lang.Object.wait(Native Method)
    at java.base/java.lang.Thread.join(Thread.java:1300)
    at java.base/java.lang.Thread.join(Thread.java:1375)
    at java.base/jdk.internal.reflect.GeneratedMethodAccessor774.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:913)
    at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:904)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:168)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodNSafe(ScriptBytecodeAdapter.java:176)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodNSpreadSafe(ScriptBytecodeAdapter.java:183)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod0SpreadSafe(ScriptBytecodeAdapter.java:198)
    at org.hidetake.groovy.ssh.interaction.Interactions.waitForEndOfStream(Interactions.groovy:97)
    at org.hidetake.groovy.ssh.interaction.Interactions$waitForEndOfStream$2.call(Unknown Source)
    at org.hidetake.groovy.ssh.operation.Command.execute(Command.groovy:83)
    at org.hidetake.groovy.ssh.operation.Operation$execute$0.call(Unknown Source)
    at org.hidetake.groovy.ssh.session.execution.Command$Helper.execute(Command.groovy:50)
    at jdk.internal.reflect.GeneratedMethodAccessor951.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite$StaticMetaMethodSiteNoUnwrapNoCoerce.invoke(StaticMetaMethodSite.java:151)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.call(StaticMetaMethodSite.java:91)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:141)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper.execute(Command.groovy:30)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper$execute$0.call(Unknown Source)
    at org.hidetake.groovy.ssh.session.SessionHandler.execute(SessionHandler.groovy)
    at jdk.internal.reflect.GeneratedMethodAccessor949.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSite.invoke(PogoMetaMethodSite.java:169)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.call(PogoMetaMethodSite.java:71)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper.execute(Command.groovy)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper$execute.call(Unknown Source)
    at org.hidetake.groovy.ssh.session.SessionHandler.execute(SessionHandler.groovy)
    at jdk.internal.reflect.GeneratedMethodAccessor948.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:384)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:166)
    at org.jenkinsci.plugins.sshsteps.SSHService$_executeCommand_closure3$_closure13.doCall(SSHService.groovy:182)
    at org.jenkinsci.plugins.sshsteps.SSHService$_executeCommand_closure3$_closure13.doCall(SSHService.groovy)
    at jdk.internal.reflect.GeneratedMethodAccessor947.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:42)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57)
    at org.hidetake.groovy.ssh.util.Utility.callWithDelegate(Utility.groovy:17)
    at jdk.internal.reflect.GeneratedMethodAccessor427.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.invoke(StaticMetaMethodSite.java:46)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.callStatic(StaticMetaMethodSite.java:102)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:214)
    at org.hidetake.groovy.ssh.session.SessionTask.wetRun(SessionTask.groovy:64)
    at jdk.internal.reflect.GeneratedMethodAccessor6430.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:158)
    at org.hidetake.groovy.ssh.session.SessionTask.call(SessionTask.groovy:48)
    at java_util_concurrent_Callable$call.call(Unknown Source)
    at org.hidetake.groovy.ssh.core.Service.run(Service.groovy:81)
    at org.hidetake.groovy.ssh.core.Service$run$1.call(Unknown Source)
    at org.jenkinsci.plugins.sshsteps.SSHService.executeCommand(SSHService.groovy:177)
    at org.jenkinsci.plugins.sshsteps.steps.CommandStep$Execution$CommandCallable.execute(CommandStep.java:84)
    at org.jenkinsci.plugins.sshsteps.util.SSHMasterToSlaveCallable.call(SSHMasterToSlaveCallable.java:32)
    at hudson.remoting.LocalChannel.call(LocalChannel.java:46)
    at org.jenkinsci.plugins.sshsteps.steps.CommandStep$Execution.run(CommandStep.java:72)
    at org.jenkinsci.plugins.sshsteps.util.SSHStepExecution.lambda$start$0(SSHStepExecution.java:84)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)
Finished: ABORTED&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히 이 에러 윗부분까지는 클라이언트의 요청을 받아서 처리해주고 있다가&lt;br&gt;한방에 서버가 다운되어버렸다.&lt;br&gt;젠킨스에서의 배포 스크립트에도 문제가 있었다.&lt;br&gt;아래의 쉘 스크립트를 젠킨스가 도커 컨테이너 내부에서 실행해주게 만들었다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
export JAVA_TOOL_OPTIONS=&quot;-Dfile.encoding='UTF8' -Duser.timezone=Asia/Seoul&quot;
kill -9 $(lsof -t -i:8080)
cd /root/server/
git pull
git checkout $1
git pull origin $1
kill -9 $(lsof -t -i:8080)
mvn clean package -P dev

java -jar -Dspring.profiles.active=dev target/server-0.0.1-SNAPSHOT.jar&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 구성으로 된 쉘 스크립트를 통해 실행을 진행했기 때문에&lt;br&gt;jar를 즉각 실행하게 만들어서 로그가 그대로 젠킨스에 전부 찍히고,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;왜?  &lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그야 당연할것인데, 로그를 실시간으로 젠킨스가 배포 과정을 찍을텐데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;java -jar&lt;/code&gt; 명령어를 백그라운드로 실행시켜주지를 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;젠킨스의 배포는 항상 finished상태가 나오질 않는 상태였다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;항상 이상태였다 ㅋㅋㅋ&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/009666b1-6c5b-4dd3-af3e-0f7a72a29cd4/image.gif&quot; alt=&quot;이미지&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gif 처음만들어봤는데 재밌네..&lt;br&gt;이부분에서 로그가 과다하게 많이 쌓이게 되어 에러를 내뱉고&lt;br&gt;was가 죽어버린 상태가 되어버렸다. (비정상적 셧다운)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://issues.jenkins.io/browse/JENKINS-45150&quot; target=&quot;_self&quot;&gt;&lt;span&gt;JENKINS-45150 large console logging can take Jenkins down or affecting performance - Jenkins Jira&lt;/span&gt;&lt;/a&gt;&lt;br&gt;검색을 진행해보니 위와같은 내용들도 얻을 수 있었다.&lt;br&gt;그래서 해결과정의 순서를 생각한 방식은 다음과 같다.&lt;/p&gt;
&lt;ol&gt; 
 &lt;li&gt;&lt;p&gt;젠킨스는 배포를 끝내서 &lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/1241acce-1032-408e-8bc8-a3a4d945a6da/image.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;이러한 화면을 만들어주어야 한다.&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;p&gt;스프링 애플리케이션의 배포 스크립트를 바꿔주어야 한다. (java -jar를 백그라운드로)&lt;/p&gt; &lt;/li&gt; 
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;이렇게 하면 되겠다!&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 전부 바꿔주게 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
export JAVA_TOOL_OPTIONS=&quot;-Dfile.encoding='UTF8' -Duser.timezone=Asia/Seoul&quot;
kill -15 $(lsof -t -i:8080)
cd /root/server/
git pull
git checkout $1
git pull origin $1
kill -15 $(lsof -t -i:8080)
mvn clean package -P dev

nohup java -jar -Dspring.profiles.active=dev target/server-0.0.1-SNAPSHOT.jar &amp;gt; ~/app.log 2&amp;gt;&amp;amp;1 &amp;amp;

echo &quot;Deploy Success&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nohup을 이용한 중단없이 실행해주고 젠킨스는 밖으로 빠져나와야 했기 때문에&lt;br&gt;이 명령어를 선택하고 실행해주었다.&lt;/p&gt;
&lt;p&gt;그와 동시에 간단하게 라이브로 볼수있게끔 기본적으로 생성되는 &lt;code&gt;nohup.out&lt;/code&gt;을 혹시몰라 만들어둔채로 마무리를 해놓았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 kill명령어를 15로 바꾸었는데,&lt;br&gt;9는 강제종료기 때문에 진행중이던 작업을 즉시 종료하고 데이터도 저장하지 않는다.&lt;br&gt;15는 자신이 하던 작업을 모두 안전하게 종료하는 절차를 밟는다.&lt;br&gt;메모리상에 있는 데이터와 각종 설정/환경 파일을 안전하게 저장한 후 프로세스를 종료한다.&lt;br&gt;15로 한다고 한들, 종료 명령어를 주게되면, 어떤 클라이언트가 요청을 보내서 작업중인 데이터도 끊어질 것이다.&lt;br&gt;그래서 spring에서 제공하는 graceful shutdown을 적용하고 kill -15를 같이 붙여주었다.&lt;br&gt;graceful shutdown은 지금 포스팅에서 다루지 않겠다.&lt;br&gt;이렇게 해서 젠킨스의 로그 과다 적재로 서버가 죽는 현상을 제거시키게 되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되어 젠킨스에 기존에 (내가 건드리지 않은) 잘못되게 설정되어 있던 것을 고치게 되니&lt;br&gt;괜찮다.&lt;br&gt;그러면서 동시에 툴도 툴마다의 각자의 할일이 있는 것인데,&lt;br&gt;CI/CD를 위한 툴에서 로그 모니터링까지 하고 있었으니 과다적재로 에러를 뱉는다는 것은&lt;br&gt;어찌보면 당연한 것이었을 수 있다고 나는 생각한다. &lt;/p&gt;</description>
      <category>디버깅</category>
      <category>Jenkins</category>
      <category>클라우드</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/113</guid>
      <comments>https://lsj8367.tistory.com/entry/Jenkins-%EC%97%90%EB%9F%AC#entry113comment</comments>
      <pubDate>Thu, 11 Aug 2022 07:30:18 +0900</pubDate>
    </item>
    <item>
      <title>쿼리 개선 2</title>
      <link>https://lsj8367.tistory.com/entry/%EC%BF%BC%EB%A6%AC-%EA%B0%9C%EC%84%A0-2</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 속도 개선&lt;br&gt;1.6초 -&amp;gt; 0.4초&lt;br&gt;메타테이블과의 조인을 해서 정보를 얻어오는 과정&lt;br&gt;서로 논리적인 데이터베이스는 다른데 mysql에서 뷰 테이블을 사용하고 있음&lt;br&gt;그런데 유저 개개인의 데이터에서 매칭시키는 메타테이블의 idx 칼럼이 인덱스가 정해져있지 않았다.&lt;br&gt;그래서 유저 개개인의 데이터를 전부 조회해서 매칭되는 데이터를 추리고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74235102/174737914-37e1829e-c72b-4e5e-a41b-a8f401dd0a5e.png&quot; alt=&quot;스크린샷 2022-06-21 오후 4 03 44&quot;&gt;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선안&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74235102/174737692-20079f7c-25df-4f45-b30e-d13995c85d31.png&quot; alt=&quot;스크린샷 2022-06-21 오후 4 04 49&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100개 이상의 row를 탐색하던 것이 25개만 탐색하는것으로 바뀐 실행이 나오게 된다.&lt;br&gt;그러면서 Extra가 제외되고 참조 정보가 인덱스로 가게 된다.&lt;br&gt;지금은 100개인 데이터였지만, 만약에 조회할 데이터가 10만 건, 100만 건 이렇게 늘어날 수록&lt;br&gt;시간은 증가했을 것이다.&lt;br&gt;진행중인 mysql 독서 스터디가 이런 생각을 하는데 도움을 많이 줬다.&lt;/p&gt;</description>
      <category>CS/데이터베이스</category>
      <category>db</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/112</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%BF%BC%EB%A6%AC-%EA%B0%9C%EC%84%A0-2#entry112comment</comments>
      <pubDate>Thu, 11 Aug 2022 07:30:07 +0900</pubDate>
    </item>
    <item>
      <title>배치 에러 개선기</title>
      <link>https://lsj8367.tistory.com/entry/%EB%B0%B0%EC%B9%98-%EC%97%90%EB%9F%AC-%EA%B0%9C%EC%84%A0%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;업무에서 Spring Batch로 세미나를 진행하고, 앱 푸시 기능을 배치로 전환하는 작업을 진행했다.&lt;br /&gt;여기에 저장하면서 글로만 보던 것들을 직접 경험해보면서 겪었던 일들을 기록하려고한다.&lt;/p&gt;
&lt;h1&gt;첫번째 에러&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 푸시 배치 서버의 구조는 스프링 스케줄러 서버에서&lt;br /&gt;푸시 서버의 api를 호출해서 해당 job들을 돌려주는 방식으로 구성이 되어있다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;물론 이 부분을 새롭게 개편해야 하는것은 맞다ㅋㅋㅋ&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 특정 시간이 되면 해당 job api로 호출을 하는데&lt;br /&gt;여기서 대략 총 데이터가 100,000건 정도 되는데 전부 동기 + 블록킹처리로 진행했다.&lt;br /&gt;그래서 스케줄러가 api를 쏘고 요청값이 최대 오래걸려도 limit을 30분을 잡았었다.&lt;br /&gt;그런데 100,000건의 데이터를 여러 로그를 쌓고, 푸시를 하는데까지 1시간이 넘게 걸렸었다.&lt;br /&gt;그래서 1시간이 지나도 받지 못하는 상황에 에러가 나서 해당 스케줄러가 돌다가 실패가 되었다. (근데 뒤에서의 푸시 서버는 계속 돌고있었다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니 다시 정리해보면 &lt;b&gt;1시간&lt;/b&gt;이 넘는 시간동안 작업을 하고 있던것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; &lt;b&gt;이부분에서 나도 대기중인 데이터만 뽑는 쿼리를 작성해야했는데 실수로 보낸 데이터까지 조회하게끔 만들었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mono 객체를 &lt;code&gt;block()&lt;/code&gt;을 사용해서 푸시를 진행했기 때문에, 블록킹 방식으로 동작하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제어권도 아예 넘겨버려서 끝이나야 다음 작업을 수행하는 형태로 진행되서 굉장히 느렸었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이방식을 &lt;code&gt;subscribe()&lt;/code&gt; 방식으로 바꿔서 논블록킹으로 푸시 발송 명령만주고 다음 작업을 진행하게끔 해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많이 속도를 줄일 수 있었다.&lt;br /&gt;-&amp;gt; 이부분은 조만간 다른 포스팅에서 자세하게 다룰 예정이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;찾은 부분&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEND, WAIT 두 상태의 데이터를 모두 가져오고 나서 SEND로 업데이트를 치고 있던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분이 일단 성능 저하의 첫번째 원인이라 생각했고,&lt;br /&gt;그리고 스케줄러는 다른 스케줄링들도 가지고 있으니, 푸시 서버에서 요청을 바로 돌려주고 해당 Job은 뒷편에서 실행해주는 것이 맞다고 생각했다.&lt;br /&gt;그래서 이 두부분을 고쳐보았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리는 WAIT상태만 추출하여 갖고 있는다.&lt;/li&gt;
&lt;li&gt;푸시서버는 기본적으로 제공해주는 JobLauncher를 배제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@RestController
@RequiredArgsConstructor
public class Demo {
    private final BasicBatchConfigurer basicBatchConfigurer;

    @PostMapping(&quot;/demo/push&quot;)
    public String appPushJobLauncher() throws Exception {
        SimpleJobLauncher jobLauncher = (SimpleJobLauncher) basicBatchConfigurer.getJobLauncher();
        jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());

        // jobLauncher 실행 로직....
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게해서 요청을 바로 수행후 return값을 먼저 돌려주었다.&lt;br /&gt;이렇게 해서 스케줄러의 동기처리로 늦어졌던 것에 대해서 일단락 짓게 되었고, &lt;br /&gt;1시간 이내로 처리가 되게 되었다.&lt;/p&gt;
&lt;h1&gt;두번째 에러&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 에러는 페이징 처리에 대한 부분이었다.&lt;br /&gt;&lt;a href=&quot;https://jojoldu.tistory.com/337&quot; target=&quot;_self&quot;&gt;&lt;span&gt;참고 블로그 링크&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;예를 들어서, 총 10페이지로 구성되어있고, 1페이지당 10개의 데이터가 있다고 가정한다.&lt;br /&gt;총 100개의 데이터가 업데이트가 되어야 한다.&lt;br /&gt;근데 커밋이 한번 일어나게 되면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;1페이지 10 -&amp;gt; 2페이지 10 -&amp;gt; 3페이지 10 -&amp;gt; ...&lt;/code&gt; 이 될줄 알았었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애초에 도입하기 이전부터 해당 참고 블로그를 보면서 이런 에러가 있구나 하면서 개발을 진행했다.&lt;br /&gt;인덱스 기준으로&lt;br /&gt;1 ~ 10 번까지의 데이터가 작업 완료되면 해당 데이터들은 Update가 진행이 될 것이다.&lt;br /&gt;그래서 11 ~ 20을 원하던 다음 데이터는 21 ~ 30을 조회하게 되는것.&lt;br /&gt;이것은 배치의 문제가 아니라 그냥 페이징 쿼리 자체의 문제라고 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 유저들에게 푸시를 보낼때 50%의 유저만 푸시를 받았을 것이다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서는 안됐다. &lt;br /&gt;방법은 우선 2가지가 있었다.&lt;br /&gt;그렇지만 나는 2번째 방법을 사용했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 왜?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 회사의 배치 서버는 JPA로 구성하기로 했었고, 그래서 JPA를 사용한 배치 동작을 구현해서&lt;br /&gt;Cursor대신 JpaPaging을 사용했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커서 사용&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커서(Cursor)란??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리문에 의해서 반환되는 결과값들을 저장하는 메모리공간&lt;br /&gt;Fetch =&amp;gt; 커서에서 원하는 결과값을 추출하는 것&lt;br /&gt;커서는 한번 커넥션을 맺은 후 커서만을 다음으로 가기 때문에 조회하고 Update되어도 갱신되는 일이 없이 적용 가능하다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Bean
@StepScope
public JpaPagingItemReader&amp;lt;Pay&amp;gt; payPagingReader() {

    private final int chunkSize = 1000;

    JpaPagingItemReader&amp;lt;PushAlarm&amp;gt; reader = new JpaPagingItemReader&amp;lt;PushAlarm&amp;gt;() {
        @Override
        public int getPage() {
            return 0;
        }
    };

    reader.setQueryString(&quot;SELECT p FROM PushAlarm p WHERE p.sendStatus = :sendStatus&quot;);
    reader.setParameterValues(Map.of(&quot;sendStatus&quot;, &quot;WAIT&quot;));
    reader.setPageSize(chunkSize);
    reader.setEntityManagerFactory(entityManagerFactory);
    reader.setName(&quot;payPagingReader&quot;);

    return reader;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 page를 0으로 고정시켜줘서 update가 일어나도 다시 0페이지만 계속 조회하는 것이다.&lt;br /&gt;이렇게 해서 문제를 해결했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치 세미나를 진행하면서 공식문서를 읽고 참고 블로그까지 더해서 학습해서&lt;br /&gt;적용해본 결과 그래도 역시 글로 보는것보다 맞으면서 배우는게 좀 더 빠르게 습득이 가능하다는걸 느낀다.&lt;br /&gt;지금은 데이터가 작아서 내가 일을 제대로 처리했을지 모르겠다.&lt;br /&gt;그래서 강의를 하나 더 들으면서 좀더 뿌리를 깊게 내려야겠다...&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Java</category>
      <category>Spring</category>
      <category>spring-batch</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/111</guid>
      <comments>https://lsj8367.tistory.com/entry/%EB%B0%B0%EC%B9%98-%EC%97%90%EB%9F%AC-%EA%B0%9C%EC%84%A0%EA%B8%B0#entry111comment</comments>
      <pubDate>Thu, 11 Aug 2022 07:29:56 +0900</pubDate>
    </item>
    <item>
      <title>DDD 표현 영역과 응용 영역</title>
      <link>https://lsj8367.tistory.com/entry/DDD-%ED%91%9C%ED%98%84-%EC%98%81%EC%97%AD%EA%B3%BC-%EC%9D%91%EC%9A%A9-%EC%98%81%EC%97%AD</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;도메인을 제대로 만들지 못하면 요구사항을 충족하는 소프트웨어를 만들기란 힘들다는 것을 잘 알고있다.&lt;br&gt;도메인 영역은 기본 패시브로 잘 구현하되,&lt;br&gt;거기에 도메인에 활력을 불어넣어줄 표현영역, 응용영역도 잘 구현이 되어야 한다.&lt;/p&gt;
&lt;h1&gt;표현 영역&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표현 영역은 사용자의 요청을 해석한다.&lt;/p&gt;
&lt;p&gt;스프링으로 따져 생각해본다면 &lt;code&gt;Controller&lt;/code&gt;로 생각하면 될 것 같다.&lt;/p&gt;
&lt;p&gt;DDD에서 말하는 패키지 구조로 보면 &lt;code&gt;interfaces&lt;/code&gt;가 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표현 영역은 URL, 요청 파라미터, 쿠키, 헤더 등등을 이용해서 클라이언트에서 원하는 작업을 받아서&lt;br&gt;응용 영역에 처리를 위임시킨다.&lt;/p&gt;
&lt;h1&gt;응용 영역&lt;/h1&gt;
&lt;p&gt;응용 영역은 표현 영역의 요청을 받아 처리를 하는 &lt;code&gt;Service&lt;/code&gt;로 생각해볼 수 있다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표현 영역에서 전달 받은 데이터는 일단 신뢰할 수 없는 데이터이므로,&lt;br&gt;값에 대한 검증이 있을 수 있고, 또 응용 영역에서 필요로 하는 데이터 타입으로 변환을 시켜주는 동작이 들어가야 한다.&lt;br&gt;그 후 응용 영역이 요구하는 객체를 생성하고 응용 서비스의 메소드를 호출한다.&lt;br&gt;그리고 작업이 완료되었을 때 반환되는 값을 토대로&lt;br&gt;응답 객체를 만들어서 알맞는 형식으로 응답을 내려준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;응용 서비스의 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보편적인 응용 서비스의 구조는 이런식이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public Response applicationMethod(final long id) {
    //1.인프라 스트럭쳐에서 애그리거트를 불러온다.
    Domain domain = domainRepository.findById(id);

    //2. 애그리거트의 도메인 기능을 실행한다.
    domain.doSomething();

    //3. 결과를 반환해준다.
    return domain.toResponse();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회해서 내려받는건 보통 이런식일거라고 생각한다.&lt;/p&gt;
&lt;p&gt;그리고 생성이나 수정같은 경우에는 &lt;code&gt;Request&lt;/code&gt; 요청 객체가 들어올때 유효성 검증을 실행하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후에 조건에 부합하는 경우에 생성, 수정을 해주면 된다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 조회인 경우 서비스 레이어가 필요하지 않은 경우에는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Controller&lt;/code&gt; -&amp;gt; &lt;code&gt;Repository&lt;/code&gt; 로만 구성해도 무방하다.&lt;/p&gt;
&lt;p&gt;기존 &lt;code&gt;Layered Architecture&lt;/code&gt; 로 구성했을 때, jpa 구현기술에 대한 의존이 있는 경우에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인이 인프라 스트럭쳐에 의존하게 된다.&lt;br&gt;DDD를 연습해보면서 느끼는 점은, 도메인에 대한 리포지토리를 인터페이스로 도출한 후에&lt;br&gt;구현체들을 인프라 스트럭쳐에 위치시키면 도메인이 구현 기술에 대한 의존이 없어지게 구성이 된다.&lt;br&gt;JpaRepository를 만든다고 한다면&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DomainRepository &amp;lt;- DomainJpaRepository&lt;/code&gt; 로 의존이 반대로 흐르게 구성이 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;값 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 검증은 표현 영역, 응용 영역 두곳에서 모두 수행이 가능하다.&lt;/p&gt;
&lt;ul&gt; 
 &lt;li&gt;표현 영역 
  &lt;ul&gt; 
   &lt;li&gt;필수 값, 값 형식, 범위 등등을 검증한다. (&lt;code&gt;@Valid&lt;/code&gt;나 &lt;code&gt;@Validated&lt;/code&gt;)&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;응용 서비스 
  &lt;ul&gt; 
   &lt;li&gt;데이터의 존재 유무와 같은 논리적 오류를 검증한다. (&lt;code&gt;findById()&lt;/code&gt;의 &lt;code&gt;orElseThrow&lt;/code&gt; 같은것들을 생각해보면 될듯????)&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;@RestControllerAdvice&lt;/code&gt;와 &lt;code&gt;@ExcepitonHandler&lt;/code&gt;를 사용해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 값에 대한 검증을 먼저 진행해준다.&lt;/p&gt;</description>
      <category>아키텍처</category>
      <category>DDD</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/110</guid>
      <comments>https://lsj8367.tistory.com/entry/DDD-%ED%91%9C%ED%98%84-%EC%98%81%EC%97%AD%EA%B3%BC-%EC%9D%91%EC%9A%A9-%EC%98%81%EC%97%AD#entry110comment</comments>
      <pubDate>Thu, 11 Aug 2022 07:29:43 +0900</pubDate>
    </item>
    <item>
      <title>DDD 도메인</title>
      <link>https://lsj8367.tistory.com/entry/DDD-%EB%8F%84%EB%A9%94%EC%9D%B8</link>
      <description>&lt;h1&gt;도메인&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인은 구현해야할 소프트웨어의 대상이다.&lt;br&gt;쇼핑몰을 생각해보면 쇼핑몰은 대상, 그리고 상품조회, 주문, 배송, 결제 등등이&lt;br&gt;하위 도메인이 된다.&lt;br&gt;그리고 도메인이라고 해서 고정된 하위 도메인이 존재하는건 아니다.&lt;br&gt;결제같은것을 PG사에 위임하니까 말이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도메인 모델 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 나는 흔히 말하는 MVC 레이어 아키텍처를 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/eddff7e8-c050-42b7-b1e1-16df2b44ef72/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.oreilly.com/library/view/software-architecture-patterns/9781491971437/ch01.html&quot; target=&quot;_self&quot;&gt;&lt;span&gt;이미지 출처 바로가기&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;단순히 &lt;code&gt;Controller&lt;/code&gt;, &lt;code&gt;Service&lt;/code&gt;, &lt;code&gt;Repository&lt;/code&gt;세개를 사용해서 구현했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 내가 넥스트스텝에서 TDD와 클린 코드를 수강했어도 변하지 못했다.&lt;br&gt;근데 이 방식이 아니라 DDD책을 보면서 주워들었던 도메인 모델 패턴을 봤다.&lt;br&gt;그래도 말만 바뀌었지 느낌은 전과 같았다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/debd1f17-781b-406d-b9c9-b1327aff4a2f/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존은 화살표 방향으로 주입이 된다.&lt;/p&gt;
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;Presentation&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;사용자의 요청을 처리하고 사용자에게 정보를 보여준다.&lt;/li&gt; 
   &lt;li&gt;사용자는 사람이 아니라 외부 시스템일 수 있다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Application&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;사용자가 요청한 기능을 실행&lt;/li&gt; 
   &lt;li&gt;비즈니스 로직을 구현하지는 않고 도메인의 계층을 조합해주기만 함&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Domain&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;&lt;strong&gt;이 설계의 가장 핵심&lt;/strong&gt;&lt;/li&gt; 
   &lt;li&gt;시스템이 제공하는 도메인들의 규칙을 구현&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;InfraStructure&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;DB나 메시징 시스템 같은 외부 시스템의 연동을 처리함&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 규칙들은 도메인 모델에만 위치하기 때문에 규칙이 바뀌거나 확장해야 된다면&lt;br&gt;다른 코드에 영향을 끼치지않고 모델들에 반영해주어야 한다.&lt;br&gt;도출한 모델들은 엔티티와 값 타입으로 구분할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;엔티티와 값타입을 제대로 구분하여야 도메인을 잘 설계하고 구현할 수 있다.  &lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;엔티티&lt;/h3&gt;
&lt;p&gt;엔티티는 &lt;strong&gt;식별자&lt;/strong&gt;를 가진다. 이 식별자는 엔티티 객체마다 고유해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 회원의 경우 회원번호가 서로 다르다.&lt;br&gt;이것이 바로 식별자가 되는것!!&lt;br&gt;다른 값이 바뀌어도 이 엔티티 객체가 생성되거나 삭제되기 전까지 유지된다.&lt;/p&gt;
&lt;p&gt;그렇기 때문에 이 식별자 값이 같다면? &lt;strong&gt;같은 객체❗️&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그렇다면 이 식별자 값으로 &lt;code&gt;equals()&lt;/code&gt;, &lt;code&gt;hashCode()&lt;/code&gt;를 재정의 할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;식별자 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 식별자를 생성해야 하는데&lt;br&gt;엔티티의 식별자를 생성하는 시점은 도메인의 특징과 사용하는 기술에 따라 달라진다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
 &lt;li&gt;특정 규칙에 따라 생성&lt;/li&gt;
 &lt;li&gt;UUID같은 고유 식별자 사용&lt;/li&gt;
 &lt;li&gt;값 직접 입력 (개인적으로 제일 안좋다 생각)&lt;/li&gt;
 &lt;li&gt;일련번호 사용 (시퀀스나 DB의 auto-increment)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배송지의 집주소가 우편번호 + 도로명주소 + 상세주소 이렇게 같이 사용하는게 대부분인데,&lt;/p&gt;
&lt;p&gt;세 가지 데이터는 다르지만 개념적으로는 하나로 일맥상통한다. 바로 &lt;strong&gt;주소&lt;/strong&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 타입은 바로 이럴때 사용하는 것이다.&lt;br&gt;이 값타입의 장점은 이 값들만의 기능을 추가할 수 있다.&lt;br&gt;이 값객체의 값을 바꾸는건 기존 데이터를 두기보단 변경이 되면 새로 생성하는&lt;br&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%B6%88%EB%B3%80%EA%B0%9D%EC%B2%B4&quot; target=&quot;_self&quot;&gt;&lt;span&gt;불변 객체&lt;/span&gt;&lt;/a&gt; 를 생성하는 방식을 선호한다.&lt;br&gt;불변으로 만드는 이유는 안전한 코드를 작성할 수 있다는 곳에 있다.&lt;br&gt;setter 메소드로 값을 바꾼다면 다른 로직에서 잘못 반영이 되는 불상사를 초래할 수 있다.&lt;br&gt;그래서 두 값 객체를 비교할 때에는 모든 인자가 같은지를 체크해주어야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무지성 Getter/Setter 남용금지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getter는 어쩔수 없는 경우가 있다고 할지라도,&lt;br&gt;setter는 값이 깨지는것을 막을수는 없다. + 핵심 개념이나 로직을 망가뜨릴수 있다.&lt;/p&gt;</description>
      <category>아키텍처</category>
      <category>DDD</category>
      <category>Java</category>
      <category>도메인주도설계</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/109</guid>
      <comments>https://lsj8367.tistory.com/entry/DDD-%EB%8F%84%EB%A9%94%EC%9D%B8#entry109comment</comments>
      <pubDate>Thu, 11 Aug 2022 07:29:31 +0900</pubDate>
    </item>
    <item>
      <title>쿼리 작성 및 최적화</title>
      <link>https://lsj8367.tistory.com/entry/%EC%BF%BC%EB%A6%AC-%EC%9E%91%EC%84%B1-%EB%B0%8F-%EC%B5%9C%EC%A0%81%ED%99%94</link>
      <description>&lt;h1&gt;Real Mysql&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;real mysql 책을 읽으면서 스터디 한 내용을 정리하고자 한다.&lt;br&gt;일단 11.1 부터 11.3까지의 내용만을 정리했다.&lt;br&gt;내가 사용하는 애플리케이션에서 특정 데이터를 테이터베이스에 저장하거나 조회를 할 때&lt;br&gt;SQL이라는 문장을 사용해야 한다.&lt;/p&gt;
&lt;p&gt;데이터베이스의 테이블이나 구조를 변경할때는 &lt;code&gt;DDL(데이터 정의 언어)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;테이블의 데이터를 조작을 위한 언어는 &lt;code&gt;DML(데이터 조작 언어)&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL작성 규칙은 mysql의 서버 시스템 정책에 따라 바뀔 수 있다.&lt;br&gt;이 정책은 데이터베이스에 어떤 테이블의 데이터들이 들어가지 않았을 때 설정하는 것이 중요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영문 대소문자 구분&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mysql에서는 설치된 운영체제에 따라서 대소문자를 구분하는데,&lt;br&gt;DB의 테이블이 디스크의 디렉토리나 파일로 매핑이 되기 때문이다.&lt;br&gt;윈도우의 명령 프롬프트에서는 대충 디렉토리를 대소문자 구분하지 않고&lt;br&gt;Tap키를 눌러 자동완성을 시키면 그냥 그 알파벳에 맞는 디렉토리를 자동완성 시킨다.&lt;br&gt;반면에 유닉스 계열에서는 대소문자를 구분해서 대문자로 시작하는 디렉토리를 소문자부터 눌러서 Tap키를 누르면 찾지를 못한다.&lt;br&gt;그래서 운영체제를 옮기면서 db를 이관할 경우 문제가 생길 수 있기 때문에&lt;/p&gt;
&lt;p&gt;Mysql 설정 파일에 &lt;code&gt;lower_case_table_name&lt;/code&gt; 시스템 변수를 설정해주면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Mysql 예약어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 테이블에 예약어와 겹치는 키워드로 생성하는 경우&lt;br&gt;역따옴표나 큰따옴표로 감싸주어야 한다.&lt;br&gt;근데 이 감싸주는 행동 때문에 애를 먹을 수 있다.&lt;br&gt;단순 조회에서도 에러가 나올텐데 이 에러가 상세 정보를 나타내주는 것이 아니라 문법 오류라고만 띄워준다고 한다.&lt;br&gt;테이블을 생성해주어야 할 때에도 역따옴표를 넣지않고 생성을 해보다가 에러를 맞는 방법이 좋을것 같다.&lt;br&gt;그리고 무엇보다 최선의 방법은 예약어 키워드와 같은 테이블을 만들지 않는것이 &lt;br&gt;가장 좋은 방법이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자열&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 문자열이 좀 신기했는데, &lt;br&gt;자동으로 다른 칼럼으로 형변환해서 비교한다는게 조금 신기했었다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT * FROM Member
WHERE number = '123';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 정수형인 컬럼에 문자열로 데이터를 조회하면&lt;br&gt;조건에 해당하는 저 문자열만 숫자로 자동 형변환이 들어가게 되니까 성능상 문제는 존재하지 않는다.&lt;br&gt;하지만 역으로 문자열 컬럼이지만 숫자데이터만 저장되어있는 경우에&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT * FROM Member
WHERE zipcode = 10001;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우편번호를 형식만 숫자인 문자열로 저장했다고 했을 때 숫자형으로 조건을 검색하면&lt;/p&gt;
&lt;p&gt;&lt;code&gt;zipcode&lt;/code&gt;에 해당하는 값을 전부 형변환하면서 하나씩 탐색하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형변환에 대한 리소스를 많이 잡아먹는다. 이렇게 비교하는건 좋지 않다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;zipcode&lt;/code&gt;가 보편적으로 숫자가 99% 이겠지만, 만약 문자가 들어간게 하나라도 있었다면 위의 조건은 에러를 뱉게 될 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DATE&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분은 따로 뗴어져서 있었지만, 마찬가지로 위에서 봤던것 처럼&lt;br&gt;이 날짜부분도 자동으로 형변환이 된다.&lt;br&gt;그래서 문자를 Date형식으로 치환하는 어떤 함수를 쓰지 않아도 된다.&lt;br&gt;그리고 문자열로 조회한다고 해서 인덱스를 못타는 것도 아니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Boolean&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이부분을 보자마자 바로 tinyint(1) 을 떠올렸다.&lt;br&gt;true는 1, false는 0으로 나타내주지만,&lt;br&gt;이것을 정수형 변수에 넣어도 동작한다.&lt;br&gt;대신 false는 딱 0만 표현이 되는데,&lt;br&gt;true라고 해서 1 이상의 값들을 표현해주지는 못한다.&lt;br&gt;그래서 사용할거라면 tinyint(1)로 제한해서 쓰는게 좋을것 같다고 봤다.&lt;/p&gt;
&lt;p&gt;더 많은 상태가 필요하다면 &lt;code&gt;Enum&lt;/code&gt;을 사용하는게 바람직하다고 생각한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Like 연산자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 연산자를 통해서 정규표현식을 사용하는 연산자보다는 좀 넓은 범위로 검색할 수 있는데 대신 인덱스를 사용할 수가 있다.&lt;/p&gt;
&lt;ul&gt; 
 &lt;li&gt;Like에서 사용하는 와일드카드 
  &lt;ul&gt; 
   &lt;li&gt;&lt;code&gt;%&lt;/code&gt; : 0 또는 1개 이상의 모든 문자에 일치하는지&lt;/li&gt; 
   &lt;li&gt;&lt;code&gt;_&lt;/code&gt; : 정확히 1개의 문자 일치&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 와일드카드들을 직접 문자열에 넣어서 탐색하고 싶다면&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ESCAPE&lt;/code&gt;를 추가해서 검색하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~로 시작하는 칼럼을 찾는 데에는 인덱스 레인지 스캔을 적용해서 탐색하는게 빠르지만, &lt;br&gt;~로 끝나는 칼럼을 찾는곳에서는 인덱스의 left-most 특성으로 인덱스 풀스캔을 진행하게 된다.&lt;br&gt;mysql의 B-Tree 인덱스를 이용한 검색은 100% 일치 또는 값의 앞부분(Left-most)만 일치하는 경우에 사용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Between 연산자&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/lsj8367/post/3cda3be4-e0a0-4f51-a8f5-ef2ae3c414c7/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 이미지는 real mysql에서 가져온 이미지이다.&lt;br&gt;왼쪽이 Between 연산, 오른쪽이 In 연산이다.&lt;br&gt;특정 조건이 명확하게 보이는경우엔 In 연산자를 적용해주는 것이 훨씬 빠를것이다.&lt;br&gt;둘다 같은 데이터를 조회할 수는 있지만 범위를 지정하기 때문에 인덱스를 타지 않고 해당 조건을 쭉 조회하게 될 것이다.&lt;/p&gt;
&lt;p&gt;다시 설명하면, 값이 불분명한 범위내에서 검색을 해야하면 Between을 사용해야 하지만, &lt;strong&gt;명확한 경우라면 In절을 사용하는것이 훨씬좋다는것&lt;/strong&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Mysql 내장함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 다른 JSON에 대한 특정 문법들에 대한 내용도 나오지만, &lt;br&gt;쓸일이 많이 없을것 같아서 읽기만 했고 제대로 봤던건&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;NOW, SYSDATE의 차이&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두개는 나는 자바를 엮어서 생각했다.&lt;br&gt;아마 그 부분이 맞을거라고 생각한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT NOW(), SLEEP(2), NOW();
SELECT SYSDATE(), SLEEP(2), SYSDATE();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NOW는 한 명령에 대해 동일한 시간을 가지고 2초를 지나서 데이터를 출력해주니까 값이 같다.&lt;br&gt;반면, SYSDATE는 한 명령이 아니라 그 자체의 함수가 있을때마다 즉각적으로 실행을 하기 때문에 두 값에 차이가 있다.&lt;br&gt;이게 이해가 잘 안된다면 아래의 자바 코드로 생각해보면 될 것 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Demo {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);
        Thread.sleep(2000);
        System.out.println(now);

        System.out.println(LocalDateTime.now());
        Thread.sleep(2000);
        System.out.println(LocalDateTime.now());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두개 방식의 차이이다.&lt;/p&gt;
&lt;p&gt;그래서 조건식에 현재 시간을 여러번 넣어야하는 경우라면 &lt;code&gt;NOW()&lt;/code&gt;를 쓰고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 위쪽에 한번 선언한것으로 전부 넣어서 조회를 해주어야 조건이 제대로 동작할 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11.3장이 MYSQL의 내장함수 설명부분이라 특정 함수들이 많아서&lt;br&gt;읽는데에 조금 분량이 많았던 것 같다.&lt;br&gt;다 같이 같은 공간에서 한번에 읽고 토론하는 시간을 가지니까&lt;br&gt;몰랐던부분도 이해하게 되고 집단지성으로 이게 이런의미구나! 라는걸 가져갈 수 있는 장점이 있다고 생각한다.&lt;br&gt;개발자에게 있어서 이 책은 2장이 더 괜찮을거라는 추천들 때문에 2장부터 보지만, 더 나아가서는 1장도 봐야 이해가 더 쉬울거라고 본다.&lt;/p&gt;</description>
      <category>CS/데이터베이스</category>
      <category>db</category>
      <category>Java</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/108</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%BF%BC%EB%A6%AC-%EC%9E%91%EC%84%B1-%EB%B0%8F-%EC%B5%9C%EC%A0%81%ED%99%94#entry108comment</comments>
      <pubDate>Thu, 11 Aug 2022 07:29:16 +0900</pubDate>
    </item>
    <item>
      <title>프로세스 상태</title>
      <link>https://lsj8367.tistory.com/entry/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%83%81%ED%83%9C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 프로그램을 실행시키면 메모리에 올라가며 프로세스가 실행된다.&lt;br&gt;지금의 컴퓨터는 수많은 프로세스들이 실행된다.&lt;br&gt;시분할 시스템이 사용되는 운영체제에서는 여러개의 프로세스를 돌아가면서 실행한다.&lt;br&gt;cpu가 병렬적으로 여러 프로세스를 막 실행시키는것이 아니라,&lt;br&gt;한 순간에 하나의 프로세스만 처리가 가능하다.&lt;br&gt;그렇지만, 그 속도가 우리가 눈치챌 수도 없을 만큼의 속도로 분할되어 실행되기 &lt;br&gt;때문에 여러 프로세스가 동시에 실행되는것 처럼 느끼게 한다.&lt;br&gt;프로세스는 시분할 시스템 처리를 위한 5가지의 상태가 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/cloudflare/lsj8367/bb9d9b72-e692-4d98-805e-3f20d77eef07/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-04-05%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.16.48.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt; 
 &lt;li&gt;&lt;p&gt;생성&lt;/p&gt; 
  &lt;ul&gt; 
   &lt;li&gt;이 상태는 PCB를 생성하고, 메모리에 프로그램 적재를 요청한 상태&lt;/li&gt; 
   &lt;li&gt;메모리에 프로그램 적재를 &lt;code&gt;승인&lt;/code&gt;받으면 &lt;code&gt;준비&lt;/code&gt;상태로 넘어간다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;p&gt;준비&lt;/p&gt; 
  &lt;ul&gt; 
   &lt;li&gt;CPU를 사용하기 위해 기다리고 있는 상태&lt;/li&gt; 
   &lt;li&gt;준비 상태의 프로세스는 CPU 스케줄러에 의해 CPU가 할당&lt;/li&gt; 
   &lt;li&gt;대부분의 프로세스가 이 상태에 존재함.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;p&gt;실행&lt;/p&gt; 
  &lt;ul&gt; 
   &lt;li&gt;CPU스케줄러에 의해 CPU를 할당 받아 실행되는 상태&lt;/li&gt; 
   &lt;li&gt;실행 상태에 있는 프로세스의 수는 &lt;strong&gt;CPU의 개수만큼&lt;/strong&gt;&lt;/li&gt; 
   &lt;li&gt;이 상태에 있는 프로세스도 CPU를 무한정 쓸 수 있는것이 아니라 할당된 시간만큼만 사용이 가능하다.&lt;/li&gt; 
   &lt;li&gt;CPU스케줄러는 할당된 시간을 초과하면 할당했던 CPU를 강제로 뺏는다. 
    &lt;ul&gt; 
     &lt;li&gt;이 때, 프로세스는 다시 &lt;code&gt;준비&lt;/code&gt; 상태로 되돌아간다.&lt;/li&gt; 
    &lt;/ul&gt; &lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;p&gt;완료&lt;/p&gt; 
  &lt;ul&gt; 
   &lt;li&gt;프로세스가 종료된 상태&lt;/li&gt; 
   &lt;li&gt;프로세스가 사용했던 데이터들을 메모리에서 제거한 후 생성된 PCB도 제거한다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;p&gt;대기&lt;/p&gt; 
  &lt;ul&gt; 
   &lt;li&gt;프로세스가 입출력요청이 있으면, 완료될 때까지 기다리는 상태&lt;/li&gt; 
   &lt;li&gt;CPU는 굉장히 빠른데 비해 입출력은 굉장히 느린작업에 속함.&lt;/li&gt; 
   &lt;li&gt;특정 프로세스가 입출력 요청을 한다면 요청이 완료될 때까지 CPU를 기다리게 하는것은 굉장히 비효율적이기 떄문에 입출력 요청을 한 프로세스를 이 상태에 두고 다른 프로세스에게 CPU를 할당함.&lt;/li&gt; 
   &lt;li&gt;시간이 지나, 입출력이 완료되면 이 대기상태에게 CPU할당 기회를 준다.&lt;/li&gt; 
   &lt;li&gt;이렇게 되면, CPU에게는 미안하지만 쉬는 시간을 주지 않고 빡세게 굴릴 수 있다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;p&gt;출처&lt;/p&gt; 
  &lt;ul&gt; 
   &lt;li&gt;이미지 : &lt;a href=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C/dashboard&quot;&gt;그림으로 쉽게 배우는 운영체제&lt;/a&gt;&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;</description>
      <category>CS/운영체제</category>
      <category>CS</category>
      <category>운영체제</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/107</guid>
      <comments>https://lsj8367.tistory.com/entry/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%83%81%ED%83%9C#entry107comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:29:00 +0900</pubDate>
    </item>
    <item>
      <title>AWS SNS 토큰 에러</title>
      <link>https://lsj8367.tistory.com/entry/AWS-SNS-%ED%86%A0%ED%81%B0-%EC%97%90%EB%9F%AC</link>
      <description>&lt;pre&gt;&lt;code&gt;Unable to load credentials from any of the providers in the chain 
AwsCredentialsProviderChain(credentialsProviders=
SystemPropertyCredentialsProvider(), EnvironmentVariableCredentialsProvider(), WebIdentityTokenCredentialsProvider(), ProfileCredentialsProvider(), ContainerCredentialsProvider(), InstanceProfileCredentialsProvider()]) : SystemPropertyCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId)., EnvironmentVariableCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId)., WebIdentityTokenCredentialsProvider(): Either the environment variable AWS_WEB_IDENTITY_TOKEN_FILE or the javaproperty aws.webIdentityTokenFile must be set., ProfileCredentialsProvider(): Profile file contained no credentials for profile default: ProfileFile(profiles=[]), ContainerCredentialsProvider(): Cannot fetch credentials from container - neither AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variables are set., InstanceProfileCredentialsProvider(): Unable to load credentials from service endpoint.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 에러가 발생했다.&lt;br&gt;앱푸시를 보내려는 토큰을 받아오는 과정인데&lt;br&gt;회사의 사수분께서 도커 안쪽에서 aws의 accessKey, secretKey 설정에 대해 들은게 없다고 하셨었는데&lt;br&gt;이 에러가 나서 운영중인 서비스 애플리케이션의 컨테이너로 들어가보니까&lt;/p&gt;
&lt;p&gt;경로는 &lt;code&gt;~/.aws&lt;/code&gt; 이었다.&lt;/p&gt;
&lt;p&gt;안에 보니까 &lt;code&gt;config&lt;/code&gt;, &lt;code&gt;credentials&lt;/code&gt; 파일이 있었는데 &lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;config&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lsj8367/post/7f6632e2-a706-457f-818f-7de72b50412e/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-17%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.17.47.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;credentials&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lsj8367/post/3be74bea-40b9-4e3a-8b83-993353743ccc/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-17%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.18.19.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두개가 존재했다. 이게 없어서 상단의 오류 메세지를 뿜어냈던 것이다.&lt;/p&gt;</description>
      <category>디버깅</category>
      <category>aws</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/106</guid>
      <comments>https://lsj8367.tistory.com/entry/AWS-SNS-%ED%86%A0%ED%81%B0-%EC%97%90%EB%9F%AC#entry106comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:28:47 +0900</pubDate>
    </item>
    <item>
      <title>@Valid, @Validated 차이</title>
      <link>https://lsj8367.tistory.com/entry/Valid-Validated-%EC%B0%A8%EC%9D%B4</link>
      <description>&lt;h1&gt;@Valid @Validated 차이&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Valid&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@Valid&lt;/code&gt;는 JSR-303표준 스펙이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;org.hibernate.validator.internal.constraintvalidators&lt;/code&gt; 안에 구현된&lt;/p&gt;
&lt;p&gt;여러 &lt;code&gt;Validator&lt;/code&gt; 구현체들로 인해 값을 검증해준다.&lt;/p&gt;
&lt;p&gt;이의 핵심은 &lt;code&gt;LocalValidatorFactoryBean&lt;/code&gt; 이며, 나는 스프링 부트를 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하였기 때문에 자동으로 구성이 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 원리&lt;/h3&gt;
&lt;p&gt;기본적으로 컨트롤러에서 &lt;code&gt;@Valid&lt;/code&gt;가 없더라도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유효성 검증을 처리하는 로직을 지나간다.&lt;br&gt;이유는??&lt;/p&gt;
&lt;p&gt;&lt;code&gt;InvocableHandlerMethod&lt;/code&gt;는 적절한 파라미터 처리기를 찾으려고&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HandlerMethodArgumentResolverComposite&lt;/code&gt;로 보낸다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lsj8367/post/779ad3f9-820a-4e0f-a297-5af17caac45b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-15%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.58.29.png&quot; alt=&quot;HandlerMethodArgumentResolverComposite&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얘가 처리해줄 resolver를 찾는데 getArgumentResolver();&lt;/p&gt;
&lt;p&gt;&lt;code&gt;private final Map&amp;lt;MethodParameter, HandlerMethodArgumentResolver&amp;gt;argumentResolverCache = new ConcurrentHashMap&amp;lt;&amp;gt;(256);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 인자에서 들어있는 &lt;code&gt;RequestResponseBodyMethodProcessor&lt;/code&gt;를 통해&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lsj8367/post/344af690-3f42-4654-bc3f-3224aa5d3537/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-16%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.00.01.png&quot; alt=&quot;RequestResponseBodyMethodProcessor&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;validation을 진행한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RequestResponseBodyMethodProcessor&lt;/code&gt;는 &lt;/p&gt;
&lt;p&gt;&lt;code&gt;AbstractMessageConverterMethodArgumentResolver&lt;/code&gt; 를 상속받고 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속받는 이 클래스의 validateIfApplicable에서 어노테이션 for 루프를 돌면서&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lsj8367/post/385d3d67-3856-45cc-b8c5-5c2b3ff62e65/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-16%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.00.42.png&quot; alt=&quot;AbstractMessageConverterMethodArgumentResolver&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Valid&lt;/code&gt;가 있는지 검색한다.&lt;/p&gt;
&lt;p&gt;있으면 &lt;code&gt;DataBinder&lt;/code&gt;객체에 넘겨서 validate를 수행한다.&lt;/p&gt;
&lt;p&gt;여기서 검증에 오류가 있으면 &lt;code&gt;MethodArgumentNotValidException&lt;/code&gt;이 발생하고,&lt;/p&gt;
&lt;p&gt;이는 스프링 &lt;code&gt;ExceptionResolver&lt;/code&gt;의 &lt;code&gt;DefaultHandlerExceptionResolver&lt;/code&gt;덕분에&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lsj8367/post/ca978b4c-1580-4d58-a40a-9d1f1af35fc9/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-16%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.04.37.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;400 에러를 뱉게된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Validated&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Validated (전역 컨트롤러에 붙임)&lt;br&gt;위의 @Valid와 다르게 cglib 그러니까 AOP기반으로 메소드 요청을&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MethodValidationInterceptor&lt;/code&gt;가 받아서 처리해준다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lsj8367/post/74efc4a3-acea-4b9f-843a-b45e3a8a3ba9/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-16%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.08.49.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;왜 cglib이냐면 &lt;code&gt;SampleController&lt;/code&gt;는 일반 클래스이므로 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스처럼 JDK 동적 프록시가 아닌 Cglib proxy를 사용한다. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lsj8367/post/e4e389eb-748d-4610-98ac-c5c1b819c802/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-16%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.08.25.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고선 이 프록시가 요청을 가로채서 유효성 검증을 진행해준다.&lt;/p&gt;
&lt;p&gt;검증을 수행하고서는 &lt;code&gt;Set&amp;lt;ConstraintViolation&amp;lt;Object&amp;gt;&amp;gt;result;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;가 비어있는 값이 아니라면 &lt;code&gt;ConstraintViolationException&lt;/code&gt;을 던져주는데&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lsj8367/post/d6427b1b-b642-4d41-9745-172c300bc077/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-16%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.11.01.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;에러 메시지의 기본값은 &lt;code&gt;javax.validation.constraints.XXX.message&lt;/code&gt; properties에 정의되어있다.&lt;/p&gt;
&lt;p&gt;이는 위처럼 &lt;code&gt;DefaultHandlerExceptionResolver&lt;/code&gt;에 등록되어 있는 객체가 아니기에&lt;/p&gt;
&lt;p&gt;500에러와 함께 밖으로 뱉어주게 된다. 별도의 &lt;code&gt;ExceptionHandler&lt;/code&gt;를 같이 구현해주어야 할것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 내가 구현한 예제 소스이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;SampleController.java&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import javax.validation.Valid;
import javax.validation.constraints.Min;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@Validated
public class SampleController {

    @PostMapping(&quot;/hello&quot;)
    public String hello(@Valid @RequestBody MessageRequest messageRequest) {
        log.info(messageRequest.getMessage());
        return &quot;hello&quot;;
    }

    @GetMapping(&quot;/hi&quot;)
    public String hi(@Min(value = 1) int value) {
        log.info(String.valueOf(value));
        return &quot;hi&quot;;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;MessageRequest.java&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import javax.validation.constraints.NotNull;
import lombok.Getter;

@Getter
public class MessageRequest {

    @NotNull(message = &quot;message는 null일 수 없습니다.&quot;)
    private String message;

}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;SampleControllerTest.java&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.lsj8367.web.request.MessageRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@WebMvcTest(SampleController.class)
class SampleControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext ctx;

    @Autowired
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(ctx)
            .alwaysDo(print())
            .build();
    }

    @Test
    @DisplayName(&quot;Post @Valid 테스트&quot;)
    void test() throws Exception {
        final String obj = objectMapper.writeValueAsString(new MessageRequest());

        mockMvc.perform(post(&quot;/hello&quot;)
                .content(obj)
                .contentType(MediaType.APPLICATION_JSON_VALUE))
            .andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName(&quot;Get @Validated 테스트&quot;)
    void hiTest() throws Exception {
        mockMvc.perform(get(&quot;/hi&quot;)
                .param(&quot;value&quot;, &quot;0&quot;)
                .contentType(MediaType.APPLICATION_JSON_VALUE)
            )
            .andExpect(status().isInternalServerError());
    }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <category>Java</category>
      <category>Spring</category>
      <category>validation</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/105</guid>
      <comments>https://lsj8367.tistory.com/entry/Valid-Validated-%EC%B0%A8%EC%9D%B4#entry105comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:28:37 +0900</pubDate>
    </item>
    <item>
      <title>AOP</title>
      <link>https://lsj8367.tistory.com/entry/AOP</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;예제는 &lt;a href=&quot;https://github.com/lsj8367/Spring-proxy&quot; target=&quot;_self&quot;&gt;&lt;span&gt;깃허브&lt;/span&gt;&lt;/a&gt;에 있다.&lt;/p&gt;
&lt;h1&gt;AOP (Aspect Oriented Programming)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP는 스프링의 핵심 구성요소중 하나이다.&lt;br&gt;관점지향 프로그래밍은 프로그램 구조에 대한 또 다른 사고방식을 제공하며&lt;br&gt;객체 지향 프로그래밍을 보완해준다.&lt;/p&gt;
&lt;ul&gt; 
 &lt;li&gt;OOP의 모듈화 핵심 단위 
  &lt;ul&gt; 
   &lt;li&gt;클래스&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;AOP의 모듈화 단위 
  &lt;ul&gt; 
   &lt;li&gt;관점 (aspect)&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP는 횡단 관심사의 분리를 허용해주어 모듈성을 높이는 것을 목표로 하는 패러다임이다.&lt;br&gt;코드 자체를 수정하지 않고 기존 코드에 추가 동작을 추가해서 수행한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개념 그리고 용어&lt;/h3&gt;
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;Aspect&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;여러 클래스에 중복되어 있는 관심사의 모듈화&lt;/li&gt; 
   &lt;li&gt;대표적인 예로 트랜잭션 관리가 있다.&lt;/li&gt; 
   &lt;li&gt;&lt;code&gt;Spring AOP&lt;/code&gt; 에서는 &lt;code&gt;@Aspect&lt;/code&gt;를 사용한다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;JoinPoint&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;메소드 실행이나 예외 처리와 같은 프로그램 실행중인 지점&lt;/li&gt; 
   &lt;li&gt;AOP에서의 &lt;code&gt;JoinPoint&lt;/code&gt;는 항상 메소드 실행을 나타냄.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Advice&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;언제 공통 관심 기능을 핵심 로직에 적용할지를 정의&lt;/li&gt; 
   &lt;li&gt;&lt;code&gt;around&lt;/code&gt;, &lt;code&gt;before&lt;/code&gt;, &lt;code&gt;after&lt;/code&gt; 등이 있음.&lt;/li&gt; 
   &lt;li&gt;AOP 프레임워크는 관점을 인터셉터로 모델링하고 유지한다.&lt;/li&gt; 
   &lt;li&gt;타깃 오브젝트에 종속되지 않는 순수한 부가기능을 담은 오브젝트&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;PointCut&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;&lt;code&gt;Advice&lt;/code&gt;가 이 포인트컷 표현식과 연관되고, 일치하는 모든 조인 포인트에서 실행되게 한다.&lt;/li&gt; 
   &lt;li&gt;JoinPoint의 상세 스펙을 정의한 것이다.&lt;/li&gt; 
   &lt;li&gt;스프링은 기본적으로 &lt;code&gt;AspectJ&lt;/code&gt;의 pointcut 표현식을 사용한다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Advisor&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;&lt;code&gt;PointCut&lt;/code&gt; 과 &lt;code&gt;Advice&lt;/code&gt;를 하나씩 가지고 있는 오브젝트&lt;/li&gt; 
   &lt;li&gt;어떤 기능을 어디에 전달할 것인지를 알고있는 가장 기본이 되는 모듈&lt;/li&gt; 
   &lt;li&gt;Spring AOP에서만 사용되는 용어&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;스프링 AOP의 특징&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 패턴 기반의 AOP 구현체, 프록시 객체를 사용하는 이유는 여러개의 부가 기능들을 추가하기 위해서 사용한다.&lt;br&gt;스프링 빈에만 AOP를 적용할 수 있다.&lt;br&gt;스프링 IoC와 연동해서 중복 코드, 프록시 패턴 구현의 번거로움, 객체간 복잡도 해결을 진행한다.&lt;br&gt;결국 프록시 패턴, 데코레이터 패턴에 대한 중복도도 제거하려고 나온것이 스프링 AOP라고 생각한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;스프링 프록시 방식의 AOP 적용&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 방식의 AOP를 적용하려면 최소 아래의 네가지 빈을 등록해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;AutoProxyCreator&lt;/li&gt;
 &lt;li&gt;Advice&lt;/li&gt;
 &lt;li&gt;PointCut&lt;/li&gt;
 &lt;li&gt;Advisor&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 스프링 프레임워크에서는 설정을 해주려면 xml에 여러가지 설정들을 해주어야 하지만,&lt;/p&gt;
&lt;p&gt;부트에서는 &lt;code&gt;build.gradle&lt;/code&gt;에 의존성 하나만 추가해주면 자동으로 설정이 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-aop
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 PointCut을 정의할 때에는 위에서 설명했던 것 처럼&lt;br&gt;AspectJ 표현식을 통해 정의해준다. 자세한건 &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts-examples&quot; target=&quot;_self&quot;&gt;&lt;span&gt;여기&lt;/span&gt;&lt;/a&gt;를 통해서 확인할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AOP 어노테이션&lt;/h3&gt;
&lt;p&gt;모든 어노테이션 뒤에는 &lt;code&gt;AspectJ&lt;/code&gt; 표현식을 사용해서 적용할 부분을 정의해준다.&lt;/p&gt;
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;@Pointcut&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;AspectJ를 적용할 타겟을 정의해준다. &lt;/li&gt; 
   &lt;li&gt;전체 컨트롤러의 함수대상, 특정 어노테이션을 설정한 함수대상, 특정 메소드 대상 등 적용하기를 원하는 범위를 정의하는 어노테이션&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;@Before&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;조건 표현식에 정의한 메소드들이 실행되기 전에 수행&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;@AfterReturning&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;적용된 타깃 메소드가 실행된 후에 수행&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;@Around&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;&lt;strong&gt;타깃 메소드 실행 전, 후&lt;/strong&gt; 처리 둘다 수행이 가능&lt;/li&gt; 
   &lt;li&gt;사용해줄 때 해당 메소드를 &lt;code&gt;ProceedingJoinPoint&lt;/code&gt;로 받아준다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프록시 팩토리의 기술 선택 방법&lt;/h3&gt;
&lt;ul&gt; 
 &lt;li&gt;대상에 인터페이스가 있으면 
  &lt;ul&gt; 
   &lt;li&gt;JDK 동적 프록시, 인터페이스 기반 프록시&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;대상에 인터페이스가 없으면 
  &lt;ul&gt; 
   &lt;li&gt;cglib, 구체 클래스 기반 프록시&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;ProxyFactory의 &lt;code&gt;setProxyTargetClass(true);&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;cglib, 구체클래스 기반 프록시, 인터페이스 여부 상관없음&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;</description>
      <category>Spring</category>
      <category>Java</category>
      <category>Spring</category>
      <category>Spring AOP</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/104</guid>
      <comments>https://lsj8367.tistory.com/entry/AOP#entry104comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:28:23 +0900</pubDate>
    </item>
    <item>
      <title>이진트리</title>
      <link>https://lsj8367.tistory.com/entry/%EC%9D%B4%EC%A7%84%ED%8A%B8%EB%A6%AC</link>
      <description>&lt;h1&gt;이진 트리 순회&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불과 반년전만 해도 이름만 들었지 마냥 먼곳에 있다고 생각했던 자료구조들이다.&lt;br&gt;근데 공부하면서 깨닫는 것은 뭐를 알아야 준비를 하고 공부도 하고&lt;br&gt;재밌게 문제도 풀 수 있다는 것이다. 그것이 바로 코딩테스트  &lt;br&gt;DFS니 BFS니 하려면&lt;br&gt;일단 스택, 큐, 배열, 재귀에 대해서 알아야된다고 생각했다.&lt;br&gt;물론 그리고 지금 포스팅하는 이 이진 트리에 대해서도 좀 짚고 넘어가야 한다고 봤다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이진트리란?&lt;/h3&gt;
&lt;p&gt;이진트리는 각각의 노드가 아래 자식 노드를 최대 두개를 가진 &lt;strong&gt;트리 자료 구조&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lsj8367/post/8b33273a-3537-4fda-9ed0-d6efcc594800/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-23%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2010.16.22.png&quot; alt=&quot;이진트리 예시&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지는 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%B4%EC%A7%84_%ED%8A%B8%EB%A6%AC&quot; target=&quot;_self&quot;&gt;&lt;span&gt;위키백과&lt;/span&gt;&lt;/a&gt; 에서 가져와봤다.&lt;br&gt;깊이(depth)는 3이고 크기는 9인 이진트리이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     1
  2     3
4  5   6  7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 구성된 트리가 있을 때 전위 표기식으로 순서를 나타내는 알고리즘을 구성해보자&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main {
    private static class Node {
        private int value;

        private Node left;
        private Node right;

        public Node(int value) {
            this.value = value;
            left = right = null;
        }
    }

    private static void dfs(Node n) {
        if (n == null) {
            return;
        }

        System.out.print(n.value + &quot; &quot;);
        dfs(n.left);
        dfs(n.right);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기 자신의 노드 그리고 left, right의 자식 노드를 알고있어서 재귀로 다음 노드를 호출하며&lt;/p&gt;
&lt;p&gt;&lt;code&gt;null&lt;/code&gt;인 경우에는 바로 &lt;code&gt;return&lt;/code&gt;을 해주어 바로 다음 로직으로 이동하게끔 구현이 되었다.&lt;/p&gt;
&lt;p&gt;전위 순회로 출력하게 되면 &lt;code&gt;1 2 4 5 3 6 7&lt;/code&gt; 순서로 나오게 된다.&lt;/p&gt;</description>
      <category>CS/자료구조</category>
      <category>graph</category>
      <category>Java</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/103</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%9D%B4%EC%A7%84%ED%8A%B8%EB%A6%AC#entry103comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:28:10 +0900</pubDate>
    </item>
    <item>
      <title>@ExceptionHandler</title>
      <link>https://lsj8367.tistory.com/entry/ExceptionHandler</link>
      <description>&lt;h1&gt;@ExceptionHandler&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외 처리기가 어떻게 동작하는지에 대해서 궁금했어서&lt;br&gt;업무중에 돌려보게 되었다. (예제코드는 다시 작성할 예정)&lt;br&gt;일단 동작과정은 DB에서 해당 id를 찾아 검색했을 때 없을 경우 예외를 던져주게 하는&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 보면&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class UserService {
    private final UserRepository userRepository;

    public User findById(final int id) {
        return userRepository.findById(id)
                             .orElseThrow(() -&amp;gt; new NotFoundException(&quot;해당 유저를 찾을 수 없습니다&quot;));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 로직을 구성했을 때 이 로직의 예외에 대한 핸들러 동작을 파보게 되었다.&lt;br&gt;일단 get 메소드로 조회 로직을 수행하고&lt;/p&gt;
&lt;p&gt;그 요청을 &lt;code&gt;RequestMappingHandlerAdapter&lt;/code&gt;로 위임해서 처리를 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 주어진 값들로 컨트롤러에 대한 로직을 처리하는데&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;InvocableHandlerMethod&lt;/h3&gt;
&lt;p&gt;다음 그림이 &lt;code&gt;InvocableHandlerMethod&lt;/code&gt; 클래스이다.&lt;/p&gt;
&lt;img width=&quot;1129&quot; alt=&quot;스크린샷 2022-02-09 오전 11 14 03&quot; src=&quot;https://user-images.githubusercontent.com/74235102/153108940-20b57033-dd9d-4034-9128-e7fc7e2760df.png&quot;&gt;
&lt;p&gt;이 때, &lt;code&gt;getBridgedMethod().invoke(getBean(), args);&lt;/code&gt; 부분이 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cglibAopProxy쪽으로 조회 로직을 맡기게 되고 그 곳에서 repository에 대한 동작을 수행하다가&lt;br&gt;findById에서 못찾았을 경우에 에러를 던지게 된다.&lt;br&gt;다시 cglibAopProxy에서 요청결과에 대한 에러를 잡아서 다시 처리되는데&lt;/p&gt;
&lt;p&gt;이것이 &lt;code&gt;InvocableHandlerMethod&lt;/code&gt;의 &lt;code&gt;catch&lt;/code&gt;로 넘어온다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;catch (InvocationTargetException ex)&lt;/code&gt; 이부분에서&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RuntimeException&lt;/code&gt;의 인자인지를 확인한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NotFoundException&lt;/h3&gt;
&lt;p&gt;이 부분에서 내가 구현한 &lt;code&gt;NotFoundException&lt;/code&gt; 의 상속도는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NotFoundException&lt;/code&gt; -&amp;gt; &lt;code&gt;NoSuchElementException&lt;/code&gt; -&amp;gt; &lt;code&gt;RuntimeException&lt;/code&gt; -&amp;gt; &lt;code&gt;Exception&lt;/code&gt; 순서기 때문에&lt;/p&gt;
&lt;p&gt;&lt;code&gt;instanceof RuntimeException&lt;/code&gt; 으로 처리가 되게 된다.&lt;/p&gt;
&lt;p&gt;그렇게해서 &lt;code&gt;exception&lt;/code&gt; 객체를 담아서 &lt;code&gt;DispatcherServlet&lt;/code&gt; 으로 넘겨주게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DispatcherServlet&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 디스패쳐 서블릿이 받은 에러를 처리해줄 누군가를 찾기 시작하는데&lt;/p&gt;
&lt;img width=&quot;962&quot; alt=&quot;스크린샷 2022-02-09 오전 11 18 55&quot; src=&quot;https://user-images.githubusercontent.com/74235102/153109412-c82ffbb7-6bdb-490a-9115-58fc354790d0.png&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HandlerExceptionResolverComposite&lt;/h3&gt;
&lt;img width=&quot;1006&quot; alt=&quot;스크린샷 2022-02-09 오전 11 19 56&quot; src=&quot;https://user-images.githubusercontent.com/74235102/153109497-1f432bd7-eee4-44ae-b946-66a6a13f2021.png&quot;&gt;
&lt;p&gt;이 두 개중 &lt;code&gt;HandlerExceptionResolverComposite&lt;/code&gt; 로 처리를 진행해준다.&lt;/p&gt;
&lt;p&gt;그렇게 해서 &lt;code&gt;resolveException&lt;/code&gt; 메소드를 실행해주는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 이미지를 보게되면 resolvers에 3개가 들어있게 된다.&lt;/p&gt;
&lt;img width=&quot;1001&quot; alt=&quot;스크린샷 2022-02-09 오전 11 20 44&quot; src=&quot;https://user-images.githubusercontent.com/74235102/153109565-d272f6b2-6a93-4aa3-8ebc-46ff0d6d9fc4.png&quot;&gt;
&lt;p&gt;그 리졸버들이 바로 &lt;code&gt;HandlerExceptionResolver&lt;/code&gt; 들을 구현한것들&lt;/p&gt;
&lt;img width=&quot;913&quot; alt=&quot;스크린샷 2022-02-09 오전 11 22 39&quot; src=&quot;https://user-images.githubusercontent.com/74235102/153109829-0a5c9a62-bc64-4c02-bc3c-c861fb5df169.png&quot;&gt;
&lt;p&gt;그중에서도 나는 &lt;code&gt;ResponseStatusException&lt;/code&gt;을 날려준게 아니기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에서 최종적으로 HandlerExceptionResolver 인터페이스 구현체인&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ExceptionHandlerExceptionResolver&lt;/code&gt;, &lt;code&gt;ResponseStatusExceptionResolver&lt;/code&gt;, &lt;code&gt;DefaultHandlerExceptionResolver&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세개를 가지고 for문을 돌게 된다&lt;/p&gt;
&lt;p&gt;&lt;code&gt;InvocableTargetException&lt;/code&gt; 이라는 객체의 target&lt;/p&gt;
&lt;p&gt;즉, 대상이 어떤 &lt;code&gt;Exception&lt;/code&gt;이냐에 따라서 &lt;code&gt;Exception&lt;/code&gt; 에맞는 &lt;code&gt;@ExceptionHandler&lt;/code&gt;로 파싱되어 커스텀된 응답으로 나가게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p&gt;음 &lt;strong&gt;똑같은 로직&lt;/strong&gt; 이라고 가정했을때 미묘하게 최적화를 하고싶다? 한다면&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HandlerExceptionResolver&lt;/code&gt; 리스트의 맨앞인 &lt;code&gt;ExceptionHandlerExceptionResolver&lt;/code&gt;를 사용 그러니까&lt;/p&gt;
&lt;p&gt;커스텀으로 &lt;code&gt;@ExceptionHandler&lt;/code&gt; 를 만들어 쓰는것이 &lt;code&gt;ResponseStatusException&lt;/code&gt; 을 던지거나, &lt;code&gt;@ResponseStatus&lt;/code&gt; 을 사용하는것보다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠를 수 있겠다.&lt;/p&gt;</description>
      <category>Spring</category>
      <category>exception</category>
      <category>Java</category>
      <category>Spring</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/102</guid>
      <comments>https://lsj8367.tistory.com/entry/ExceptionHandler#entry102comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:27:26 +0900</pubDate>
    </item>
    <item>
      <title>Select Sort  선택정렬</title>
      <link>https://lsj8367.tistory.com/entry/Select-Sort-%EC%84%A0%ED%83%9D%EC%A0%95%EB%A0%AC</link>
      <description>&lt;h1&gt;선택 정렬 (Select Sort)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택 정렬은 현재 위치에 들어갈 데이터를 찾아 선택하는 알고리즘이다.&lt;br&gt;오름차순을 기준으로 정렬한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제자리 정렬의 알고리즘 중 하나이다.&lt;br&gt;정렬 되지 않은 입력된 배열 외에 다른 메모리를 사용하지 않는다.&lt;br&gt;해당하는 n번째에 넣을 정렬된 원소 자리는 이미 정해져있고, &lt;br&gt;어떤 값을 넣을지를 선택하는 알고리즘이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 과정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
 &lt;li&gt;주어진 배열에서 최솟값을 찾는다.&lt;/li&gt;
 &lt;li&gt;그 최솟값을 배열의 맨 앞의 수와 자리를 교체해준다.&lt;/li&gt;
 &lt;li&gt;맨 처음 값을 뺀 나머지 배열로부터 최솟값을 찾는다.&lt;/li&gt;
 &lt;li&gt;교체한 다음 맨 앞의 배열과 값을 바꿔준다.&lt;/li&gt;
 &lt;li&gt;이 과정을 정렬이 완료될 때까지 계속 반복한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74235102/152795661-8ade8adb-15f0-495f-929f-7f7895ac6281.png&quot; alt=&quot;스크린샷 2022-02-07 오후 10 19 41&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보기 좋은 예시 이미지를 가져와봤다.&lt;br&gt;이제 그러면 구현을 해보도록 하자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Select Sort 구현&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;n 길이를 가진 배열을 생성하고 다음줄에 n개의 숫자를 입력받아 선택정렬 한다.&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class SelectionSort {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine()); //길이가 n
        StringTokenizer st = new StringTokenizer(br.readLine());

        int[] arr = new int[n]; //n개의 숫자를 넣을 배열

        for (int i = 0; i &amp;lt; n; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }

        final int[] results = solution(arr);

        for (int result : results) {
            System.out.print(result);
        }
    }

    private static int[] solution(int[] arr) {
        for (int i = 0; i &amp;lt; arr.length - 1; i++) {
            int minIndex = i;
            for (int j = i + 1; j &amp;lt; arr.length; j++) {
                if (arr[j] &amp;lt; arr[minIndex]) {
                    minIndex = j;
                }
            }

            //최소값과 해당 반복 index의 맨 앞자리와 치환
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
        return arr;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 사소한 지식이라도 안적어두면 까먹고 또 기억이 안나는것 같다.&lt;br&gt;위의 예시도 ide도움 없이 손코딩으로 포스팅을 올려보는데 더 상기되서 까먹지 않을것 같다.&lt;br&gt;계속 까먹기 때문에 이렇게 기록으로 남겨두는것이 좋을듯 하다.  &lt;/p&gt;</description>
      <category>CS/알고리즘</category>
      <category>array</category>
      <category>Java</category>
      <category>sort</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/101</guid>
      <comments>https://lsj8367.tistory.com/entry/Select-Sort-%EC%84%A0%ED%83%9D%EC%A0%95%EB%A0%AC#entry101comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:27:15 +0900</pubDate>
    </item>
    <item>
      <title>자료구조</title>
      <link>https://lsj8367.tistory.com/entry/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</link>
      <description>&lt;h1&gt;자료구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료 구조의 사전적인 의미는,&lt;br&gt;효율적인 접근 및 수정을 가능케하는 자료의 조직, 관리, 저장을 의미한다.&lt;br&gt;그래서 이 자료구조는,&lt;br&gt;데이터들의 값 모임, 데이터간 관계 들을 의미한다.&lt;br&gt;이 사전적 의미를 보니까 알고리즘과 뗄래야 뗄 수가 없는거 같다.&lt;br&gt;결국 이 선택에 따라 효율적으로 알고리즘을 설계를 할 수 있으니까 말이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 자료구조들은 당연히 여러 가지가 있으며, 각각의 자료구조는 각자의 연산과 목적에 맞춰져 있다.&lt;/p&gt;
&lt;p&gt;단순히 알고리즘 문제들만 풀 때 자료구조를 선택한다? 답은 &lt;strong&gt;NO&lt;/strong&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떠한 기능을 설계함에 있어 자료구조를 선택하는 것은 필수일 것이다.&lt;br&gt;자꾸 예를 알고리즘으로 들어서 그렇지만, 이만큼 알고리즘과 뗄래야 뗄 수가 없다는것.&lt;br&gt;자료구조가 명확해지면, 그에 따라오는 알고리즘이 반드시 필요한게 있을 거라고 본다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;종류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료의 특성, 크기, 사용법, 연산에 따라 여러가지 종류가 있다.&lt;br&gt;크게 단순 구조, 선형 구조, 비선형 구조, 파일 구조로 나눌 수 있다.&lt;/p&gt;
&lt;img width=&quot;664&quot; alt=&quot;스크린샷 2022-02-01 오후 9 42 40&quot; src=&quot;https://user-images.githubusercontent.com/74235102/151970304-4e5e0465-180c-48e9-ba76-3e641a6fe712.png&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단순 구조&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;True/False, 정수, 실수, 문자 및 문자열과 같이 컴퓨터가 기본적으로 제공하는 자료형&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선형 구조&lt;/h3&gt;
&lt;ul&gt; 
 &lt;li&gt;Array(배열) 
  &lt;ul&gt; 
   &lt;li&gt;가장 일반적인 구조&lt;/li&gt; 
   &lt;li&gt;메모리 상에 같은 타입의 자료가 연속적으로 저장&lt;/li&gt; 
   &lt;li&gt;자료값을 나타내는 가장 작은 단위&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;LinkedList 
  &lt;ul&gt; 
   &lt;li&gt;노드를 하나의 단위로 한다.&lt;/li&gt; 
   &lt;li&gt;노드는 자료와 다음 노드를 가리키는 참조값으로 구성&lt;/li&gt; 
   &lt;li&gt;&lt;strong&gt;점 조직&lt;/strong&gt; 같은 느낌&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;Stack 
  &lt;ul&gt; 
   &lt;li&gt;후입선출(Last-In-First-Out)&lt;/li&gt; 
   &lt;li&gt;먼저 저장된 것이 마지막에 나오게 되는구조&lt;/li&gt; 
   &lt;li&gt;자료의 나열 순서를 바꾸고 싶다면 스택에 넣었다가 꺼내면 역순으로 변경된다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;Queue 
  &lt;ul&gt; 
   &lt;li&gt;선입선출(First-In-First-Out)&lt;/li&gt; 
   &lt;li&gt;먼저 저장된 것이 먼저 나오게 되는 구조&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;Deque 
  &lt;ul&gt; 
   &lt;li&gt;양쪽에서 넣기 빼기를 할 수 있는 구조&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비 선형 구조&lt;/h3&gt;
&lt;ul&gt; 
 &lt;li&gt;Graph 
  &lt;ul&gt; 
   &lt;li&gt;꼭짓점과 꼭짓점을 잇는 변으로 구성된다. &lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;Tree 
  &lt;ul&gt; 
   &lt;li&gt;뿌리와 뿌리 또는 다른 꼭짓점을 하나의 부모로 갖는 꼭짓점들로 이루어진 구조&lt;/li&gt; 
   &lt;li&gt;부모와 자식의 관계는 변으로 표현 &lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드디스크 같은 보조 기억장치에 저장되는 파일에 대한 자료구조&lt;br&gt;메모리에 한번에 로드할 수 없는 대용량의 자료&lt;br&gt;파일 구성 방식에 따라 순차, 색인, 직접으로 나뉘게 된다.&lt;/p&gt;</description>
      <category>CS/자료구조</category>
      <category>자료구조</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/100</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0#entry100comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:26:59 +0900</pubDate>
    </item>
    <item>
      <title>Spring Rest Docs 테스트로 문서화를 해보자!</title>
      <link>https://lsj8367.tistory.com/entry/Spring-Rest-Docs-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A1%9C-%EB%AC%B8%EC%84%9C%ED%99%94%EB%A5%BC-%ED%95%B4%EB%B3%B4%EC%9E%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Spring</category>
      <category>Spring-Rest-Docs</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/99</guid>
      <comments>https://lsj8367.tistory.com/entry/Spring-Rest-Docs-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A1%9C-%EB%AC%B8%EC%84%9C%ED%99%94%EB%A5%BC-%ED%95%B4%EB%B3%B4%EC%9E%90#entry99comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:26:48 +0900</pubDate>
    </item>
    <item>
      <title>업무 리팩토링에 대한 회고</title>
      <link>https://lsj8367.tistory.com/entry/%EC%97%85%EB%AC%B4-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81%EC%97%90-%EB%8C%80%ED%95%9C-%ED%9A%8C%EA%B3%A0</link>
      <description>&lt;h1&gt;회고록&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그를 옮기게 되어 날짜가 맞지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성일 : 2022년 2월 21일&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;br /&gt;스타트업인지라, 먼저 있었던 사람들을 욕할건 아니다.&lt;br /&gt;그렇지만 지금 그렇게 쳐나가면서 생겼던 기술부채로 인해서 신규 개발건이 들어왔을 때&lt;br /&gt;유지보수가 힘든 점들이 많다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무책임했던 누군가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 있던 백엔드 개발자가 나가고 없었기에 모르겠지만 그사람은&lt;/p&gt;
&lt;p&gt;&lt;del&gt;자바에 엄청난 자부심이 있다고 했다.&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;&amp;lt;-- 이렇게 하면 안되지만 정말 못짰다.. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협업이라는 것을 싫어했고, 자기의 의견이 맞았으며, 코딩 컨벤션 또한 지켜진게 없었다.&lt;br /&gt;그리고 외주로 뭔가 개발이 왔던건줄 알았던 코드가 그 사람의 코드였다...&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;악취나는 코드의 예시들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스가 &lt;code&gt;MAIN_CLASS&lt;/code&gt;, &lt;code&gt;main_class&lt;/code&gt;, &lt;code&gt;MainClass&lt;/code&gt;, &lt;code&gt;mainClass&lt;/code&gt; 등등... 네이밍이 상당했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 나의 사수분께서는 JDBC -&amp;gt; JPA로 리팩토링을 하고 계셨던 찰나에 내가 입사를 했던 것이다.&lt;br /&gt;많이 봐서 익숙했던 HTTP 상태 코드도 쓰지 않고 커스텀으로 200을 정의했는데 이게 에러였나? 그랬다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;switch(STATUS_CODE) {
    case 200: 
    case 201: 
    case 202: 
    case 203: 
    case 204: 
    // 이렇게 엄청 많이 있었다
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 아마 300번까지 있나 그런다.. 너무 보기싫다..  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 &lt;code&gt;ResponseBody&lt;/code&gt;를 전역에서 설정해준것이 바로 &lt;code&gt;@RestController&lt;/code&gt;가 아닌가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난 내 지식이 의심스러울 정도였다.&lt;br /&gt;간략하게나마 써보자면...&lt;br /&gt;예를들어 유저를 조회하려는 API가 있다고 하면..&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나름의 컨벤션이 있던건 같다. 클래스 뒤에 1은 create, 2는 read, 3은 delete, 4는 update였다 ㅋㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직도 웃기다..  &lt;br /&gt;아 아래 예시는 조회인데 조회 API 클래스만 따로있다.&lt;br /&gt;그러니까 위에서 했던 1, 2, 3, 4 일련의 동작들이 하나로 엮인게 아니라&lt;br /&gt;1기능 당 1개의 컨트롤러다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@RestController
public class h_user_info_2 {

    @ResponseBody
    @RequestMapping(value = &quot;/pood/user/view/2&quot;, method = RequestMethod.POST)
    public String USER_INFO_LIST_VIEW(@RequestBody h_user_info_vo2 header) { // 이런식으로 받아온다.. + 조회인데 POST + api 뒤에 숫자도 붙는다..
        // 왜 실행하고 있는지 모르겠는 로직...
        // 뭐 대략 이런식으로 돌아간다
        return response;
    }
} &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 뭐 이런식으로 구성이 되어있었으니 상당히 머리아프고 파악도 못해먹겠다.&lt;br /&gt;그래서 사수도 엄청 답답했을 것이다.&lt;br /&gt;이걸 JPA로 바꾼 사수님은  &lt;br /&gt;하지만 그래도 이거로는 부족했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체지향적으로 코드를 구성해보자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 바꾼것, 가독성을 높인건 사실 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 여기서도 문제가 있었는데 흔히 보는 JPA의 Repository인 &lt;code&gt;UserRepository&lt;/code&gt; (예시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Repository를 어떤 서비스에서 필요로 할 때마다 의존 주입을 해서 쓰고있던것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 이제 이것도 나는 바꿔야 겠다고 마음을 먹었고, &lt;code&gt;하나의 서비스 -&amp;gt; 하나의 리파지토리&lt;/code&gt; 를 의존하는 것이 가장 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 생각을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;code&gt;여러 비즈니스 로직을 담는 클래스 -&amp;gt; 서비스&lt;/code&gt; 인것 같은 뉘앙스가 많이 풍겼다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/74235102/151166655-a1a1e825-6bbe-470a-aa03-08e9616307e9.png&quot; alt=&quot;스크린샷 2022-01-26 오후 9 55 21&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 볼 수 있는 &lt;code&gt;Layered Architecture&lt;/code&gt; 로 구성이 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 참조가 너무 많은거다.   이 상황에서 퍼사드 패턴을 떠올렸다.  &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;code&gt;Controller&lt;/code&gt; -&amp;gt; &lt;code&gt;Facade&lt;/code&gt; -&amp;gt; &lt;code&gt;Service&lt;/code&gt; -&amp;gt; &lt;code&gt;Repository&lt;/code&gt; 로 의존의 흐름을 넘기는 것을 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 여러 서비스 클래스를 한데모아 &lt;code&gt;Facade&lt;/code&gt;에서 각 서비스들을 조합해서 붙여주는 식으로 정리를 했더니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존 방향도 틀이 맞춰지고, 서비스단과 리파지토리 단위테스트를 쉽게 가져갈 수 있게 되었다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
public class Controller {
    private final Facade facade;

    public Controller(final Service service) {
        this.service = service;
    }
}

@Facade // 어노테이션을 새로 정의해주었다
public class Facade {
    private final Service1 service1;
    private final Service2 service2;
    private final Service3 service3;

    public Facade(final Service1 service1, final Service2 service2, final Service3 service3) {
        this.service1 = service1;
        this.service2 = service2;
        this.service3 = service3;
    }
}

// 이런게 3개 있다.
public class Service1 {
    private final Repository1 repository1;

    public Service1(final Repository1 repository1) {
        this.repository1 = repository1;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이렇게 구성을 하게 되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 전체가 더러워질 바에는 서비스가 더러워져선 안된다고 생각을 했고,&lt;br /&gt;많은 사람들에게 자문을 구한 결과도 다들 방금 한 얘기를 다들 하셨다. 감사합니다~  &lt;br /&gt;무언가를 호출해서 모아주는 집약체인 퍼사드 클래스가 더러워지게 된거다.&lt;br /&gt;기존 코드에 비해 엄청나게 개선 되었다고 생각한다. 중구난방인 의존을 한데 모았기 때문이다.&lt;br /&gt;아무튼 한달반동안 많이 성장한것 같고 더 성장해야겠다.&lt;br /&gt;갈 길이 멀고 나는 아직 배가 고프다. 더 많이 더 빨리 더 높게 성장하고싶다    &lt;/p&gt;</description>
      <category>Diary</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/98</guid>
      <comments>https://lsj8367.tistory.com/entry/%EC%97%85%EB%AC%B4-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81%EC%97%90-%EB%8C%80%ED%95%9C-%ED%9A%8C%EA%B3%A0#entry98comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:26:37 +0900</pubDate>
    </item>
    <item>
      <title>Monolithic vs MSA</title>
      <link>https://lsj8367.tistory.com/entry/Monolithic-vs-MSA</link>
      <description>&lt;p&gt;&lt;code&gt;모놀리식&lt;/code&gt;과 &lt;code&gt;MSA&lt;/code&gt;에 대해 차이를 정리해 보겠다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MSA&lt;/code&gt;가 등장하기 이전에 하나의 서비스로 하나의 애플리케이션을 만드는 것.&lt;/p&gt;
&lt;p&gt;이게 바로 &lt;code&gt;Monolithic Architecture(모놀리식 아키텍처)&lt;/code&gt; 라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MSA&lt;/code&gt;는 &lt;code&gt;Micro Service Architecture&lt;/code&gt; 의 줄임말로 하나의 큰 애플리케이션을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 애플리케이션으로 나눠서 만드는 아키텍쳐이다.&lt;/p&gt;
&lt;h1&gt;Monolithic Architecture&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사전적 정의를 보면&lt;/p&gt;
&lt;img width=&quot;678&quot; alt=&quot;스크린샷 2022-01-18 오후 10 55 13&quot; src=&quot;https://user-images.githubusercontent.com/74235102/149950603-3934011b-6d64-469a-9a31-13343cb9e8c8.png&quot;&gt;
&lt;p&gt;&lt;code&gt;단단이 짜여 하나로 되어있는&lt;/code&gt; 이라는 뜻으로 나와있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 이 애플리케이션의 규모는 거대하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;로컬 환경에서의 개발 편리성&lt;/li&gt;
 &lt;li&gt;통합 시나리오 테스트 용이&lt;/li&gt;
 &lt;li&gt;배포 간단&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt; 
 &lt;li&gt;코드의 수정 및 추가가 힘들다&lt;/li&gt; 
 &lt;li&gt;효율적 자원관리가 힘들다&lt;/li&gt; 
 &lt;li&gt;자주 업데이트 불가능&lt;/li&gt; 
 &lt;li&gt;신기술 적용의 힘듦&lt;/li&gt; 
 &lt;li&gt;부분적인 서버의 장애 -&amp;gt; 전체 장애로 번짐&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Scale Out&lt;/code&gt; 이 불가능하다&lt;/li&gt; 
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt; 
 &lt;p&gt;Scale Out이란?&lt;/p&gt; 
 &lt;p&gt;&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 운영중에 있을 때 사용자가 갑자기 급격하게 증가 할 때,&lt;br&gt;더 많은 서버 용량, 그리고 성능이 필요해지는데&lt;br&gt;이 때, 서버를 여러대 추가하여 확장시키는 방법을 말한다.&lt;br&gt;반대로 Scale Up 이 있는데, &lt;br&gt;이는 여러대를 추가하는 방식이 아니라 단순하게 보면&lt;br&gt;그냥 서버 컴퓨터의 성능을 증가시켜주는 것이다.&lt;/p&gt;
&lt;img width=&quot;567&quot; alt=&quot;스크린샷 2022-01-18 오후 11 03 07&quot; src=&quot;https://user-images.githubusercontent.com/74235102/149951901-589e2630-0e6e-427c-aa94-955d89029d81.png&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모놀리식의 장단점을 보면 단순하다.&lt;br&gt;모놀리식, MSA 모두 각각의 장단점이 있기 때문에 상황에 따라 달라질 수 있다는 점은 주의해야한다.&lt;br&gt;일단 하나의 애플리케이션이기 때문에 단순해서 좋다. 그래서 소규모 개발일 때는 모놀리식이 더 적합할 것 같다.&lt;br&gt;근데 이 프로젝트의 규모가 커진다면 단순해지면 안좋아 진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 프로그래밍을 하다보면 의존성이라는 단어는 참 많이 접하게 된다.&lt;br&gt;이것이 아키텍쳐에도 적용하여 생각하면, 너무 많은 기능들을 구현했고,&lt;br&gt;그 기능들이 하나의 애플리케이션에 묶여있으니까, 서로간의 의존성이 높아지고&lt;br&gt;이해할 수 없는 어려운 코드도 만들어 질 것이다.&lt;br&gt;결국 이런 문제를 빚어 커지면 커질 수록 고려하게 되는 것이 바로 MSA이다.&lt;/p&gt;
&lt;h1&gt;Micro Service Architecture&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어들만 봐도 의미를 알 수 있겠다.&lt;br&gt;현 회사에 도메인은 여러가지가 있다.&lt;br&gt;주문, 배송, 결제, 등등... 커머스이기 때문에 들어가는 당연한 도메인들이다.&lt;/p&gt;
&lt;p&gt;이런 도메인들을 하나씩 분리하는 것이 바로 &lt;code&gt;MSA&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt; 
 &lt;li&gt;빌드 및 테스트 시간 단축&lt;/li&gt; 
 &lt;li&gt;유연한 기술 적용 
  &lt;ul&gt; 
   &lt;li&gt;어떤 한 언어와 프레임워크에 종속되지 않는다. &lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Scale Out&lt;/code&gt; 가능&lt;/li&gt; 
 &lt;li&gt;서비스간 연관성 낮음 
  &lt;ul&gt; 
   &lt;li&gt;한 서버의 문제가 다른 서비스에 영향을 끼치지 않는다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt; 
 &lt;li&gt;성능 이슈 
  &lt;ul&gt; 
   &lt;li&gt;모놀리식의 경우 다른 기능을 호출할 때 메소드를 호출한다.&lt;/li&gt; 
   &lt;li&gt;&lt;code&gt;MSA&lt;/code&gt;는 네트워크 비용이 발생하게 된다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;트랜잭션 
  &lt;ul&gt; 
   &lt;li&gt;다른 서버들간의 트랜잭션 처리를 할 경우 불편해질 수 있다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;개발 시간 증가 
  &lt;ul&gt; 
   &lt;li&gt;서버가 분리됨에 따라 관리가 필요해짐&lt;/li&gt; 
   &lt;li&gt;여러가지 신경을 많이 쓰며 관리해주어야 한다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 MSA가 좋다? 그건 아니다. 이렇게 놓고보니 정답도 없다.&lt;br&gt;이거는 알 수 있었다.  &lt;br&gt;모든 것을 다 분리한다기 보다는&lt;br&gt;하나의 애플리케이션중 특정 서비스 부분의 트래픽이 월등하게 많다면?  &lt;br&gt;그러면 그 부분을 따로 떼어내서 서버를 스케일 아웃해서 증가시키고&lt;br&gt;기준을 트래픽이로 잡아 분리하는 게 좋아보인다.&lt;br&gt;그리고 무엇보다 근본인 프로젝트, 회사 규모에 따라 상황에 맞는 적절한 아키텍쳐를 잡아가는 것이 바람직한 선택이다.&lt;/p&gt;</description>
      <category>아키텍처</category>
      <author>리승자이</author>
      <guid isPermaLink="true">https://lsj8367.tistory.com/97</guid>
      <comments>https://lsj8367.tistory.com/entry/Monolithic-vs-MSA#entry97comment</comments>
      <pubDate>Wed, 10 Aug 2022 19:26:24 +0900</pubDate>
    </item>
  </channel>
</rss>