<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코딩하는 흑구</title>
    <link>https://sas-study.tistory.com/</link>
    <description>코딩하는 흑구의 프로그래밍 학습 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Mon, 13 Apr 2026 22:14:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코딩하는흑구</managingEditor>
    <image>
      <title>코딩하는 흑구</title>
      <url>https://tistory1.daumcdn.net/tistory/3039730/attach/d849a862b4ee4a43abe3cf3fa8029156</url>
      <link>https://sas-study.tistory.com</link>
    </image>
    <item>
      <title>[PostgreSQL] Insert On Conflict (jpa save / upsert 구현)</title>
      <link>https://sas-study.tistory.com/502</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;이번 주제는 postgresql의 upsert를 구현해보는 포스팅입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 jpa를 쓸때 save() 메소드는 없으면 insert, 있으면 update를 한다고 알고 있는데요.&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;JPA는 대략 아래처럼 SimpleJpaRepository에 구현되어 있습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@Transactional
@Override
public &amp;lt;S extends T&amp;gt; S save(S entity) {

   Assert.notNull(entity, &quot;Entity must not be null.&quot;);

   if (entityInformation.isNew(entity)) {
      em.persist(entity);
      return entity;
   } else {
      return em.merge(entity);
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;entity 정보가 isNew에 참이면 persist - 등록하고 아니면 merge를 하게 됩니다. 이건 이제 JPA로 넘어온 어플리케이션 영역이고, sql 단에서는 어떻게 이것을 구현할 수 있을지 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;pre class=&quot;sql&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;create table room (
    seq serial primary key,
    column_1 varchar(10),
    column_2 varchar(10),
    column_3 varchar(10),
    column_4 varchar(10)
);

create table room_new_data (
    room_seq integer
        primary key
        references room,
    column_1 varchar(10),
    column_2 varchar(10)
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 테이블인 &lt;b&gt;room&lt;/b&gt;과 room의 &lt;u&gt;primary key를 참조&lt;/u&gt;하는 &lt;b&gt;room_new_dat 테이블&lt;/b&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;room 테이블은 기존에 별도의 데이터가 아래와 같이 들어있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMVRCP/btr4uQT1I13/fMXE9CrX6fkQd3b24cjupK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMVRCP/btr4uQT1I13/fMXE9CrX6fkQd3b24cjupK/img.png&quot; data-alt=&quot;부모 테이블 room&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMVRCP/btr4uQT1I13/fMXE9CrX6fkQd3b24cjupK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMVRCP%2Fbtr4uQT1I13%2FfMXE9CrX6fkQd3b24cjupK%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;1414&quot; height=&quot;248&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;부모 테이블 room&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 room 테이블의 레코드를 채웠으니 room_new_data가 room 테이블을 참조할 데이터가 생겼습니다.&lt;/p&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;1162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BXiEa/btr4uI9GpM3/GUfgEdBtWqCBtv0KRG60m1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BXiEa/btr4uI9GpM3/GUfgEdBtWqCBtv0KRG60m1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BXiEa/btr4uI9GpM3/GUfgEdBtWqCBtv0KRG60m1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBXiEa%2Fbtr4uI9GpM3%2FGUfgEdBtWqCBtv0KRG60m1%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;2022&quot; height=&quot;1162&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;1162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 시간에 두개의 세션에서 다음과 같은 insert SQL을 동시에 날렸다면.. 먼저 커밋된 Transaction A가 정상적으로 커밋되고 Transaction B에서는 Error가 발생하였습니다. 재시도를 해도 동일한 에러메시지가 노출되어 insert 구문을 완성할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 저의 요구사항은 어쨌든 가장 최근 정보로 column이 들어가야하는데 거의 동시에 두개의 트랜잭션이 데이터를 삽입하고자 하니 primary 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;이때 Transaction B의 데이터가 최종적으로 select SQL에서 노출되게 하기 위해서는 어떻게 해야할까요? DBMS가 insert 쿼리를 받는 순간 위의 JPA 로직처럼 개발자가 제어할 수 있는 방법은 더이상 없습니다. 단순한 Insert 쿼리로는요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 제어를 위해 On Conflict 문이 존재합니다. 쿼리를 아래와 같이 수정해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;1098&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfdFbP/btr4tPOXzGA/SZBJw8tMifvX3umEK9jKlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfdFbP/btr4tPOXzGA/SZBJw8tMifvX3umEK9jKlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfdFbP/btr4tPOXzGA/SZBJw8tMifvX3umEK9jKlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfdFbP%2Fbtr4tPOXzGA%2FSZBJw8tMifvX3umEK9jKlk%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;2022&quot; height=&quot;1098&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;1098&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;pre class=&quot;pgsql&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;insert into room_new_data (room_seq, column_1, column_2)
values (1, 'data_1', 'data_1')
ON CONFLICT (room_seq)
DO UPDATE SET
    column_1 = 'data_1',
    column_2 = 'data_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;저의 경우 충돌이 예상되는 room_seq 컬럼을 on conflict를 주고 실행하다 conflict가 발생하는 상황에는 DO 다음에 있는 행위를 하라는 의미의 SQL을 작성하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;근데 보통 jdbcTemplate이나 native query, mybatis 등 쿼리문을 직접 작성하여 삽입할 데이터를 바인딩할 때, values 절에다가만 기재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저렇게 update 문에도 하나하나 적어줄 수 있지만 dbms가 이러한 키워드를 제공합니다. (&lt;b&gt;excluded&lt;/b&gt;)&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;insert into room_new_data (room_seq, column_1, column_2)
values (1, 'data_1', 'data_1')
ON CONFLICT (room_seq)
DO UPDATE SET
    column_1 = excluded.column_1,
    column_2 = excluded.column_2
;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;excluded는 마치 특정 참조처럼 동작하여 column1, column2에 삽입하려던 데이터를 바인딩해줍니다.&lt;/p&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 방법은 컬럼 기준으로 동작하는 것이고 컬럼이 여러가지에 묶여 SQL문이 복잡하게 보인다면 constraint로 깔끔하게 줄일 수도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;insert into room_new_data (room_seq, column_1, column_2)
values (1, 'data_1', 'data_1')
ON CONFLICT ON CONSTRAINT room_new_data_pkey
DO UPDATE SET
    column_1 = excluded.column_1,
    column_2 = excluded.column_2
;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 room_new_data_pkey는 테이블을 만들때 기본으로 생성하는 primary key의 name입니다. 따라서 해당 제약조건이 위배되었을 경우 dbms는 이를 conflict라고 판단하여 아래의 update문을 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DO NOTHING 이라는 행위도 있는데 참고삼아 한번 알아보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;insert into room_new_data (room_seq, column_1, column_2)
values (1, 'data_1', 'data_1')
ON CONFLICT ON CONSTRAINT room_new_data_pkey
DO NOTHING;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DO NOTHING의 경우 conflict에 대한 제약조건의 위배가 발동되었을 경우, &lt;u&gt;아무행위도 하지 않습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;u&gt;최초로 삽입되는 경우의 insert 쿼리만 허용&lt;/u&gt;한다면 해당 조건은 괜찮을 것 같습니다. 해당 쿼리가 어떤 요구사항을 갖는지에 따라 이러한 clause의 사용이 의미가 있을지 없을지가 결정이 된다고 생각하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;DO NOTHING의 경우에 처음에는 아무런 행위도 하지 않는다는 것이 조금 껄끄럽다고 생각했었는데. insert 쿼리의 동시성 이슈가 발생하는 상황에서 어느 비즈니스를 가져가느냐에 따라 다른 것 같네요.&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;</description>
      <category>Database/Postgresql</category>
      <category>on conflict</category>
      <category>on conflict on constraint</category>
      <category>postgresql on conflict</category>
      <category>postgresql upsert</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/502</guid>
      <comments>https://sas-study.tistory.com/502#entry502comment</comments>
      <pubDate>Fri, 17 Mar 2023 23:38:09 +0900</pubDate>
    </item>
    <item>
      <title>[Kibana] 키바나 백그라운드 데몬으로 실행하기(node.js, nvm, pm2)</title>
      <link>https://sas-study.tistory.com/501</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;우선 키바나를 실행시키기 위한 명령어는 bin 디렉토리 안에 있는 kibana 파일을 실행해주면 됩니다.&lt;/p&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;a href=&quot;https://www.elastic.co/guide/en/kibana/master/start-stop.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.elastic.co/guide/en/kibana/master/start-stop.html&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1678631872366&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;Start and stop Kibana | Kibana Guide [master] | Elastic&quot; data-og-description=&quot;Start and stop Kibanaedit The method for starting and stopping Kibana varies depending on how you installed it. Archive packages (.tar.gz)edit If you installed Kibana on Linux or Darwin with a .tar.gz package, you can start and stop Kibana from the command&quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/guide/en/kibana/master/start-stop.html&quot; data-og-url=&quot;https://www.elastic.co/guide/en/kibana/master/start-stop.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/kibana/master/start-stop.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/guide/en/kibana/master/start-stop.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;Start and stop Kibana | Kibana Guide [master] | Elastic&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Start and stop Kibanaedit The method for starting and stopping Kibana varies depending on how you installed it. Archive packages (.tar.gz)edit If you installed Kibana on Linux or Darwin with a .tar.gz package, you can start and stop Kibana from the command&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&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;리눅스 명령어를 다루거나 리눅스 내에서 어떤 소프트웨어를 설치하고 기동하면서 많이 봤을 systemctl 과 service를 제시하기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 저는 kibana 안쪽 디렉토리에 node와 node_modules, package.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;우선 package.json을 확인하여 키바나가 구동하는 node.js 버전을 확보해두겠습니다.&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;키바나 루트 디렉토리&lt;b&gt;&lt;br /&gt;&lt;br /&gt;cat bin/package.json&lt;br /&gt;&lt;/b&gt;&amp;gt;&amp;gt; &quot;node&quot;: &quot;14.15.4&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력된 package.json에서 node 프로퍼티의 값을 보니 14.15.4 버전이었습니다. 그러므로 저는 이 키바나를 실행시키기 위해서는 14.15.4 버전의 node.js가 필요합니다.&lt;/p&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;https://sas-study.tistory.com/499&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/499&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1678634314850&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;[Node.js] nvm 설치하고 특정 node.js 가져오기&quot; data-og-description=&quot;안녕하세요. node js 버전을 관리하는 nvm 을 설치해보겠습니다. nvm은 Node Version Management의 약자로 깃허브 주소는 아래에 있습니다. https://github.com/nvm-sh/nvm GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compli&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/499&quot; data-og-url=&quot;https://sas-study.tistory.com/499&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bplHoz/hyRTSP7ky9/6Ekmm1oI5vV8ysKjxBuAK0/img.png?width=800&amp;amp;height=255&amp;amp;face=0_0_800_255,https://scrap.kakaocdn.net/dn/ngbpU/hyRTN88CfY/4ZjBBtuIMGMGkMT0nud7G0/img.png?width=800&amp;amp;height=255&amp;amp;face=0_0_800_255,https://scrap.kakaocdn.net/dn/18Zkk/hyRU3idAql/EKZ3gOzmOmwQMMG5EZ7iSk/img.png?width=1692&amp;amp;height=540&amp;amp;face=0_0_1692_540&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/499&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/499&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bplHoz/hyRTSP7ky9/6Ekmm1oI5vV8ysKjxBuAK0/img.png?width=800&amp;amp;height=255&amp;amp;face=0_0_800_255,https://scrap.kakaocdn.net/dn/ngbpU/hyRTN88CfY/4ZjBBtuIMGMGkMT0nud7G0/img.png?width=800&amp;amp;height=255&amp;amp;face=0_0_800_255,https://scrap.kakaocdn.net/dn/18Zkk/hyRU3idAql/EKZ3gOzmOmwQMMG5EZ7iSk/img.png?width=1692&amp;amp;height=540&amp;amp;face=0_0_1692_540');&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;[Node.js] nvm 설치하고 특정 node.js 가져오기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. node js 버전을 관리하는 nvm 을 설치해보겠습니다. nvm은 Node Version Management의 약자로 깃허브 주소는 아래에 있습니다. https://github.com/nvm-sh/nvm GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compli&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sas-study.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;node 버전 14.15.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;하지만 실행할 노드가 확보되었을 뿐 노드자체를 데몬으로 실행하기 위해서는 별도의 소프트웨어가 필요합니다. node.js를 데몬으로 실행하기 위해서는 pm2가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래의 포스팅을 통해 pm2를 설치해주시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/500&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/500&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1678634382662&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;[Node.js] PM2 설치하기(Node.js 백그라운드 실행) 및 사용법&quot; data-og-description=&quot;안녕하세요. 이번 포스팅에서는 PM2를 설치하여 Node.js를 백그라운드로 실행하는 방법을 확인해보겠습니다. 현재 저는 kibana 관련 작업중이고요. node.js로 실행할 수 있는 kibana를 백그라운드로 실&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/500&quot; data-og-url=&quot;https://sas-study.tistory.com/500&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/n1qbd/hyRU7ZeeUM/JHOXKh3VswDjGAskkz76J1/img.png?width=800&amp;amp;height=346&amp;amp;face=0_0_800_346,https://scrap.kakaocdn.net/dn/cuVxom/hyRVf31vbw/bpFk4n9nHUN08LcPQmWBs1/img.png?width=800&amp;amp;height=346&amp;amp;face=0_0_800_346,https://scrap.kakaocdn.net/dn/blv3QY/hyRVcTLXNU/eDD0u2LzakflUky5TrUVY1/img.png?width=1886&amp;amp;height=1604&amp;amp;face=0_0_1886_1604&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/500&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/500&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/n1qbd/hyRU7ZeeUM/JHOXKh3VswDjGAskkz76J1/img.png?width=800&amp;amp;height=346&amp;amp;face=0_0_800_346,https://scrap.kakaocdn.net/dn/cuVxom/hyRVf31vbw/bpFk4n9nHUN08LcPQmWBs1/img.png?width=800&amp;amp;height=346&amp;amp;face=0_0_800_346,https://scrap.kakaocdn.net/dn/blv3QY/hyRVcTLXNU/eDD0u2LzakflUky5TrUVY1/img.png?width=1886&amp;amp;height=1604&amp;amp;face=0_0_1886_1604');&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;[Node.js] PM2 설치하기(Node.js 백그라운드 실행) 및 사용법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 이번 포스팅에서는 PM2를 설치하여 Node.js를 백그라운드로 실행하는 방법을 확인해보겠습니다. 현재 저는 kibana 관련 작업중이고요. node.js로 실행할 수 있는 kibana를 백그라운드로 실&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sas-study.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;위에서 pm2를 이용해서 kibana 어플리케이션을 데몬으로 실행할 수 있었습니다.&lt;/p&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;style3&quot;&gt;- kibana 실행&lt;b&gt;&lt;br /&gt;pm2&amp;nbsp;start&amp;nbsp;kibana-7.11.1-linux-x86_64/src/cli/cli.js&amp;nbsp;--name&amp;nbsp;kibana&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;- kibana 실행중지&lt;br /&gt;&lt;b&gt;pm2 stop kibana&lt;/b&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;이렇게 된다면 특정 디렉토리를 기준으로 kibana 디렉토리 내에 스크립트를 작성할 수도 있을 것 같습니다.&lt;/p&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>ELK/kibana</category>
      <category>kibana 데몬 실행</category>
      <category>kibana 백그라운드 실행</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/501</guid>
      <comments>https://sas-study.tistory.com/501#entry501comment</comments>
      <pubDate>Mon, 13 Mar 2023 00:22:26 +0900</pubDate>
    </item>
    <item>
      <title>[Node.js] PM2 설치하기(Node.js 백그라운드 실행) 및 사용법</title>
      <link>https://sas-study.tistory.com/500</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;이번 포스팅에서는 PM2를 설치하여 Node.js를 백그라운드로 실행하는 방법을 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 저는 kibana 관련 작업중이고요. node.js로 실행할 수 있는 kibana를 백그라운드로 실행하기 위해서 키바나 버전과 호환되는 node를 설치하였고 이를 백에서 실행할 수 있도록 PM2를 설치하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 PM2 메인 홈페이지로 이동해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pm2.keymetrics.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://pm2.keymetrics.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1678633183408&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;PM2 - Home&quot; data-og-description=&quot;Advanced process manager for production Node.js applications. Load balancer, logs facility, startup script, micro service management, at a glance.&quot; data-og-host=&quot;pm2.keymetrics.io&quot; data-og-source-url=&quot;https://pm2.keymetrics.io/&quot; data-og-url=&quot;https://pm2.keymetrics.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bM39BQ/hyRU3oYy6X/ga4n92AkQdL5O23lt9JXkk/img.png?width=710&amp;amp;height=200&amp;amp;face=0_0_710_200,https://scrap.kakaocdn.net/dn/pHfpZ/hyRTQx0a6F/IsgKkxSRWmsYixcEQuwU1k/img.png?width=1895&amp;amp;height=1004&amp;amp;face=0_0_1895_1004,https://scrap.kakaocdn.net/dn/Raj2H/hyRTWZjTBH/d22vZpDDwHmbiJgTvt6Elk/img.png?width=727&amp;amp;height=561&amp;amp;face=0_0_727_561&quot;&gt;&lt;a href=&quot;https://pm2.keymetrics.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pm2.keymetrics.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bM39BQ/hyRU3oYy6X/ga4n92AkQdL5O23lt9JXkk/img.png?width=710&amp;amp;height=200&amp;amp;face=0_0_710_200,https://scrap.kakaocdn.net/dn/pHfpZ/hyRTQx0a6F/IsgKkxSRWmsYixcEQuwU1k/img.png?width=1895&amp;amp;height=1004&amp;amp;face=0_0_1895_1004,https://scrap.kakaocdn.net/dn/Raj2H/hyRTWZjTBH/d22vZpDDwHmbiJgTvt6Elk/img.png?width=727&amp;amp;height=561&amp;amp;face=0_0_727_561');&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;PM2 - Home&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Advanced process manager for production Node.js applications. Load balancer, logs facility, startup script, micro service management, at a glance.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pm2.keymetrics.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;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-6696145815750706&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 화면을 보니 현재 페이지에서는 설치해야될 명령어를 알려주고 있습니다.(npm을 사용하면 된다고 하네요.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2872&quot; data-origin-height=&quot;1244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecyP67/btr3eBRicTV/J4Z7GkiXJKqqxG8k6knwI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecyP67/btr3eBRicTV/J4Z7GkiXJKqqxG8k6knwI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecyP67/btr3eBRicTV/J4Z7GkiXJKqqxG8k6knwI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FecyP67%2Fbtr3eBRicTV%2FJ4Z7GkiXJKqqxG8k6knwI0%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;700&quot; height=&quot;303&quot; data-origin-width=&quot;2872&quot; data-origin-height=&quot;1244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 npm 명령어를 단독으로 입력해서 npm 명령어를 서버가 인식하는지 확인해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm은 노드가 설치되어있어야 하니 우선 여러분의 서버에 노드가 없다면 아래의 포스팅을 보시고 따라해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/499&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/499&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1678633323786&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;[Node.js] nvm 설치하고 특정 node.js 가져오기&quot; data-og-description=&quot;안녕하세요. node js 버전을 관리하는 nvm 을 설치해보겠습니다. nvm은 Node Version Management의 약자로 깃허브 주소는 아래에 있습니다. https://github.com/nvm-sh/nvm GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compli&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/499&quot; data-og-url=&quot;https://sas-study.tistory.com/499&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bplHoz/hyRTSP7ky9/6Ekmm1oI5vV8ysKjxBuAK0/img.png?width=800&amp;amp;height=255&amp;amp;face=0_0_800_255,https://scrap.kakaocdn.net/dn/ngbpU/hyRTN88CfY/4ZjBBtuIMGMGkMT0nud7G0/img.png?width=800&amp;amp;height=255&amp;amp;face=0_0_800_255,https://scrap.kakaocdn.net/dn/18Zkk/hyRU3idAql/EKZ3gOzmOmwQMMG5EZ7iSk/img.png?width=1692&amp;amp;height=540&amp;amp;face=0_0_1692_540&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/499&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/499&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bplHoz/hyRTSP7ky9/6Ekmm1oI5vV8ysKjxBuAK0/img.png?width=800&amp;amp;height=255&amp;amp;face=0_0_800_255,https://scrap.kakaocdn.net/dn/ngbpU/hyRTN88CfY/4ZjBBtuIMGMGkMT0nud7G0/img.png?width=800&amp;amp;height=255&amp;amp;face=0_0_800_255,https://scrap.kakaocdn.net/dn/18Zkk/hyRU3idAql/EKZ3gOzmOmwQMMG5EZ7iSk/img.png?width=1692&amp;amp;height=540&amp;amp;face=0_0_1692_540');&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;[Node.js] nvm 설치하고 특정 node.js 가져오기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. node js 버전을 관리하는 nvm 을 설치해보겠습니다. nvm은 Node Version Management의 약자로 깃허브 주소는 아래에 있습니다. https://github.com/nvm-sh/nvm GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compli&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sas-study.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;blockquote data-ke-style=&quot;style3&quot;&gt;- 설치 명령어(npm)&lt;b&gt;&lt;br /&gt;&lt;br /&gt;npm&amp;nbsp;install&amp;nbsp;pm2&amp;nbsp;-g&lt;/b&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;이제 pm2를 통해 Node.js를 백그라운드로 실행해보겠습니다.&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;- pm2 프로세스 시작&lt;b&gt;&lt;br /&gt;&lt;br /&gt;pm2 start ${Node.js 파일명}&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;pm2&amp;nbsp;start&amp;nbsp;kibana-7.11.1-linux-x86_64/src/cli/cli.js&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우는 pm2가 실행할 노드js 대상이 명령어를 입력하는 디렉토리로부터 위의 디렉토리 구조를 띄고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;1604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3E1LZ/btr3dcLhZoZ/u3tBlkFZ2PYlX2HtTmQjq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3E1LZ/btr3dcLhZoZ/u3tBlkFZ2PYlX2HtTmQjq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3E1LZ/btr3dcLhZoZ/u3tBlkFZ2PYlX2HtTmQjq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3E1LZ%2Fbtr3dcLhZoZ%2Fu3tBlkFZ2PYlX2HtTmQjq0%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;1886&quot; height=&quot;1604&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;1604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예쁘장한 화면이 나오면서 status가 online이라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kibana를 실행하는 것도 잘 되었습니다.(필자 확인)&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;- pm2 관리 현황&lt;br /&gt;&lt;b&gt;&lt;br /&gt;pm2 status&lt;/b&gt;&lt;/blockquote&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;1886&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Inkou/btr3eALFmAG/ggOCKPCjuJrabU86BOGqi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Inkou/btr3eALFmAG/ggOCKPCjuJrabU86BOGqi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Inkou/btr3eALFmAG/ggOCKPCjuJrabU86BOGqi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FInkou%2Fbtr3eALFmAG%2FggOCKPCjuJrabU86BOGqi1%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;1886&quot; height=&quot;208&quot; data-origin-width=&quot;1886&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종료를 할때는 name 칸에 있는 이름을 입력해주면 됩니다.&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;- pm2 프로세스 종료&lt;b&gt;&lt;br /&gt;&lt;br /&gt;pm2&amp;nbsp;stop&amp;nbsp;cli&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btz2LI/btr3eyNSLxf/cRmLf2Me8PRwCASZ1XMOTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btz2LI/btr3eyNSLxf/cRmLf2Me8PRwCASZ1XMOTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btz2LI/btr3eyNSLxf/cRmLf2Me8PRwCASZ1XMOTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbtz2LI%2Fbtr3eyNSLxf%2FcRmLf2Me8PRwCASZ1XMOTk%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;1886&quot; height=&quot;280&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;280&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;그런데 한번 등록이 되면 pm2가 이를 자기 메모리 안에서 관리를 하게 됩니다. 이를 제거해보겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- pm2 관리대상 제거&lt;br /&gt;&lt;b&gt;&lt;br /&gt;pm2&amp;nbsp;delete&amp;nbsp;cli&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KSHyo/btr3kxUVzDN/luuVs9rRUlD8ZbAXYOjBK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KSHyo/btr3kxUVzDN/luuVs9rRUlD8ZbAXYOjBK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KSHyo/btr3kxUVzDN/luuVs9rRUlD8ZbAXYOjBK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKSHyo%2Fbtr3kxUVzDN%2FluuVs9rRUlD8ZbAXYOjBK1%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;1886&quot; height=&quot;202&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;202&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;pm2가 관리하는 name이 보통 node.js 파일명을 기준으로 생성하기 때문에 별도의 alias가 필요할 수 있습니다. 저의 경우 cli.js 파일을 실행하는 것이지만 실상 이 실행파일은 kibana를 실행하는 것이기 때문에 name 칸에 kibana가 나오도록 해보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- pm2 실행 name 옵션 지정&lt;b&gt;&lt;br /&gt;&lt;br /&gt;pm2 start ${Node.js 파일명} --name ${name}&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;pm2&amp;nbsp;start&amp;nbsp;kibana-7.11.1-linux-x86_64/src/cli/cli.js&amp;nbsp;--name&amp;nbsp;kibana&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pe4Yb/btr3ofGsjHN/J7d36NDl5XVNI6MkwS1lrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pe4Yb/btr3ofGsjHN/J7d36NDl5XVNI6MkwS1lrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pe4Yb/btr3ofGsjHN/J7d36NDl5XVNI6MkwS1lrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpe4Yb%2Fbtr3ofGsjHN%2FJ7d36NDl5XVNI6MkwS1lrk%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;1886&quot; height=&quot;266&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name 칸에 kibana가 뜨는 것을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 node.js 파일이면 동일하게 적용할 수 있으니 무난하게 실행하실 수 있을 것입니다.&lt;/p&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>웹 개발/Javascript</category>
      <category>node pm2 install</category>
      <category>node pm2 데몬 실행</category>
      <category>node pm2 백그라운드</category>
      <category>pm2 백그라운드</category>
      <category>pm2 설치</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/500</guid>
      <comments>https://sas-study.tistory.com/500#entry500comment</comments>
      <pubDate>Mon, 13 Mar 2023 00:18:00 +0900</pubDate>
    </item>
    <item>
      <title>[Node.js] nvm 설치하고 특정 node.js 가져오기</title>
      <link>https://sas-study.tistory.com/499</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;node js 버전을 관리하는 nvm 을 설치해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nvm은 Node Version Management의 약자로 깃허브 주소는 아래에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/nvm-sh/nvm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/nvm-sh/nvm&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1678632259828&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 - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions&quot; data-og-description=&quot;Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions - GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active nod...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/nvm-sh/nvm&quot; data-og-url=&quot;https://github.com/nvm-sh/nvm&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHV33A/hyRVaBDfUY/v0AkxfbQZhk8bJEP15kjZK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/nvm-sh/nvm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/nvm-sh/nvm&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHV33A/hyRVaBDfUY/v0AkxfbQZhk8bJEP15kjZK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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 - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions - GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active nod...&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;설치하는 방법은 여러가지가 있지만&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;&lt;b&gt;curl&amp;nbsp;-o-&amp;nbsp;&lt;a href=&quot;https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh&lt;/a&gt;&amp;nbsp;|&amp;nbsp;bash&lt;/b&gt;&lt;br /&gt;혹은&lt;br /&gt;&lt;b&gt;wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash&lt;/b&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;저는 wget을 통해 설치하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브에 제시된 내용에 따르면 설치완료되면 .bashrc 파일을 수정하라고 하지만 이미 아래의 내용이 기재되어 있는 것을 확인할 수 있을 것입니다.(혹시나 안되어있다면 복붙해주세요.)&lt;/p&gt;
&lt;pre class=&quot;perl&quot; style=&quot;background-color: #000000; color: #c9d1d9; text-align: start;&quot;&gt;&lt;code&gt;export NVM_DIR=&quot;$([ -z &quot;${XDG_CONFIG_HOME-}&quot; ] &amp;amp;&amp;amp; printf %s &quot;${HOME}/.nvm&quot; || printf %s &quot;${XDG_CONFIG_HOME}/nvm&quot;)&quot;
[ -s &quot;$NVM_DIR/nvm.sh&quot; ] &amp;amp;&amp;amp; \. &quot;$NVM_DIR/nvm.sh&quot; # This loads nvm&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;그리고 .bashrc 파일을 건드린 것이기 때문에 source 명령어로 적용을 해주어야 합니다.&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;&lt;b&gt;source&amp;nbsp;.bashrc&lt;/b&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;그리고 명령어로 nvm을 입력해서 여러 옵션들이 뜨는 것을 확인해줍니다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특정 Node.js 버전 설치하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 제가 원하는 특정 Node.js 버전을 설치해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9Ulen/btr3oet0BI5/P7ktuzuIi8o7yA9fkL6I6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9Ulen/btr3oet0BI5/P7ktuzuIi8o7yA9fkL6I6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9Ulen/btr3oet0BI5/P7ktuzuIi8o7yA9fkL6I6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9Ulen%2Fbtr3oet0BI5%2FP7ktuzuIi8o7yA9fkL6I6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;540&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브의 Usage 내용을 보니 특정 버전의 Node를 설치하기 위해서는&amp;nbsp;nvm install {버전} 을 입력하라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 14.15.4 버전을 설치해야하므로 아래의 명령어로 입력해주겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;nvm&amp;nbsp;install&amp;nbsp;14.15.4&lt;br /&gt;&lt;br /&gt;nvm list&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Akz6S/btr3cB5HhpK/XbLm5Y3g8k3pxF5gwjKev1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Akz6S/btr3cB5HhpK/XbLm5Y3g8k3pxF5gwjKev1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Akz6S/btr3cB5HhpK/XbLm5Y3g8k3pxF5gwjKev1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAkz6S%2Fbtr3cB5HhpK%2FXbLm5Y3g8k3pxF5gwjKev1%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;942&quot; height=&quot;546&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list 명령어로 default 버전이 14.15.4 버전이 된 것을 확인할 수 있습니다.&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;&lt;b&gt;node&amp;nbsp;-v&lt;/b&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;&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;</description>
      <category>웹 개발/Javascript</category>
      <category>install node nvm</category>
      <category>node management</category>
      <category>node 설치</category>
      <category>Node.js 관리</category>
      <category>nvm node install</category>
      <category>nvm 설치</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/499</guid>
      <comments>https://sas-study.tistory.com/499#entry499comment</comments>
      <pubDate>Sun, 12 Mar 2023 23:55:50 +0900</pubDate>
    </item>
    <item>
      <title>[Kibana] 키바나 설치하기 및 엘라스틱서치 연동</title>
      <link>https://sas-study.tistory.com/498</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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://sas-study.tistory.com/492&quot;&gt;엘라스틱서치 설치하기&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://sas-study.tistory.com/493&quot;&gt;엘라스틱서치 환경설정(Bootstrap Checks)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://sas-study.tistory.com/495&quot;&gt;엘라스틱서치 클러스터 구성하기&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://sas-study.tistory.com/496&quot;&gt;엘라스틱서치 클러스터 내 노드 통신에 TLS 적용(Elasticsearch security)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://sas-study.tistory.com/497&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;엘라스틱서치 시스템 유저 비밀번호 설정하기.(elasticsearch-setup-passwords)&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;이번엔 엘라스틱서치를 시각화 할 수 있는 키바나를 설치해보겠습니다.&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;우선 위의 목차에서 엘라스틱서치 노드 3개를 클러스터링하여 구성하였고 1번 노드만이 클라이언트와의 통신을 위해 9200 포트를 오픈해놓았습니다.&lt;/p&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번 노드에 키바나를 설치해보고자 합니다. (아마 1번노드에 설치해도 되긴 할 것 같습니다. 메모리가 4G 이상이기만 하면요.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Kibana 설치&lt;/b&gt;&lt;/h4&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;https://www.elastic.co/kr/downloads/past-releases#kibana&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.elastic.co/kr/downloads/past-releases#kibana&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1678440530437&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;Past Releases of Elastic Stack Software&quot; data-og-description=&quot;Looking for a past release of Elasticsearch, Logstash, Kibana, es-hadoop, Shield, Marvel, or our language clients? You're in the right place.&quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/kr/downloads/past-releases#kibana&quot; data-og-url=&quot;https://www.elastic.co/kr/downloads/past-releases#kibana&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eAldZ/hyRSFi1LeX/jJLVBqYWg8XRr5hbOxk2bk/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/hH6sX/hyRTMU7D6J/j2VCwhkhBkJI8FMogf4gg0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/downloads/past-releases#kibana&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/kr/downloads/past-releases#kibana&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eAldZ/hyRSFi1LeX/jJLVBqYWg8XRr5hbOxk2bk/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/hH6sX/hyRTMU7D6J/j2VCwhkhBkJI8FMogf4gg0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837');&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;Past Releases of Elastic Stack Software&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Looking for a past release of Elasticsearch, Logstash, Kibana, es-hadoop, Shield, Marvel, or our language clients? You're in the right place.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 기존 엘라스틱서치 버전과 일치하는 7.11.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;엘라스틱서치를 설치했던 것과 동일하게 GCP 서버 스펙을 고려하여 Linux 64 비트의 링크를 복사해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cckmtF/btr3ccQ0b5r/KgOMsPBOIrkwRJPLjPvwLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cckmtF/btr3ccQ0b5r/KgOMsPBOIrkwRJPLjPvwLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cckmtF/btr3ccQ0b5r/KgOMsPBOIrkwRJPLjPvwLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcckmtF%2Fbtr3ccQ0b5r%2FKgOMsPBOIrkwRJPLjPvwLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1004&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;1004&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엘라스틱서치를 설치했던과 마찬가지로 wget을 활용해서 키바나를 설치해보겠습니다.&lt;/p&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;wget&amp;nbsp;&lt;a href=&quot;https://artifacts.elastic.co/downloads/kibana/kibana-7.11.1-linux-x86_64.tar.gz&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://artifacts.elastic.co/downloads/kibana/kibana-7.11.1-linux-x86_64.tar.gz&lt;/a&gt;&amp;nbsp;./&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 명령어를 통해 키바나 7.11.1 버전의 압축파일을 다운로드받고 아래의 명령어로 unzip 해주면 됩니다.&lt;/p&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;tar&amp;nbsp;xfz&amp;nbsp;kibana-7.11.1-linux-x86_64.tar.gz&lt;/b&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;키바나의 디렉토리 구조를 보면 엘라스틱서치와 유사하게 config 디렉토리와 bin 디렉토리가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bin디렉토리 안에 있는 kibana 파일로 키바나를 실행시킬 수 있으며&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;config 디렉토리 안에 있는 kibana.yml 파일로 이전에 엘라스틱서치 설정을 건드렸던 것처럼 키바나가 시각화할 엘라스틱서치를 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Kibana 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 kibana.yml 파일을 수정하겠습니다.&lt;/p&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 data-ke-style=&quot;style3&quot;&gt;vi config/kibana.yml&lt;br /&gt;&lt;br /&gt;server.host:&amp;nbsp;&quot;blackdog-elastic-3&quot;&lt;br /&gt;server.name:&amp;nbsp;&quot;blackdog-kibana&quot;&lt;br /&gt;elasticsearch.hosts:&amp;nbsp;[&quot;http://blackdog-elastic-3:9200&quot;]&lt;br /&gt;&lt;br /&gt;elasticsearch.username:&amp;nbsp;&quot;kibana_system&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 서버 호스트와 키바나 서버명을 입력해줍니다.(server.host, server.name)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 시각화할 엘라스틱서치의 주소를 입력해줍니다. 해당 키바나는 3번 노드와 통신할 것이기 때문에 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;그리고 elasticsearch에 접근하기 위해 지난 시간 설정해놓은 kibana 유저명을 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/497&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/497&lt;/a&gt; 해당 링크에 나온 설명중 키바나 시스템 유저명은 kibana_system 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패스워드는 엘라스틱서치와 마찬가지로 keystore를 이용하겠습니다.&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;bin/kibana-keystore&amp;nbsp;create&lt;br /&gt;bin/kibana-keystore&amp;nbsp;add&amp;nbsp;elasticsearch.password&lt;br /&gt;&lt;br /&gt;&amp;gt;&amp;gt; 엘라스틱서치에서 유저 keystore에 입력했던 패스워드를 입력해주세요. 저는 blackdog을 입력했었습니다.&lt;br /&gt;(이 부분 혹시 헷갈리시면 kibana_system 유저로 엘라스틱서치에 찔러봐서 데이터가 잘 나오는지 확인해보면 됩니다.)&lt;br /&gt;&lt;br /&gt;bin/kibana-keystore&amp;nbsp;list&lt;br /&gt;(출력) elasticsearch.password&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;잘 실행이 되는 것을 확인할 수 있고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/491&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/491&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1678443408734&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;[GCP] VM 인스턴스 방화벽 설정(port 설정)&quot; data-og-description=&quot;안녕하세요. 지난 포스팅에서는 - [GCP] VM 인스턴스 생성하기 - [GCP] VM 인스턴스를 터미널 도구를 이용하여 SSH 접속 설정 두가지 내용을 설정해봤는데요. 저같은 경우는 이제 ssh로 접속해서 elastics&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/491&quot; data-og-url=&quot;https://sas-study.tistory.com/491&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oQwA8/hyRSKEDkfg/y61dMpyoYBM2uOngK8YaXk/img.png?width=800&amp;amp;height=1145&amp;amp;face=0_0_800_1145,https://scrap.kakaocdn.net/dn/xmsOE/hyRSHuovEO/4r7inbkyMBkrnqtKkkkMIk/img.png?width=800&amp;amp;height=1145&amp;amp;face=0_0_800_1145,https://scrap.kakaocdn.net/dn/5rTXK/hyRSRcG8Wq/jlEQvCISaNwu1CTjtwkas1/img.png?width=1838&amp;amp;height=582&amp;amp;face=0_0_1838_582&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/491&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/491&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oQwA8/hyRSKEDkfg/y61dMpyoYBM2uOngK8YaXk/img.png?width=800&amp;amp;height=1145&amp;amp;face=0_0_800_1145,https://scrap.kakaocdn.net/dn/xmsOE/hyRSHuovEO/4r7inbkyMBkrnqtKkkkMIk/img.png?width=800&amp;amp;height=1145&amp;amp;face=0_0_800_1145,https://scrap.kakaocdn.net/dn/5rTXK/hyRSRcG8Wq/jlEQvCISaNwu1CTjtwkas1/img.png?width=1838&amp;amp;height=582&amp;amp;face=0_0_1838_582');&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;[GCP] VM 인스턴스 방화벽 설정(port 설정)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 지난 포스팅에서는 - [GCP] VM 인스턴스 생성하기 - [GCP] VM 인스턴스를 터미널 도구를 이용하여 SSH 접속 설정 두가지 내용을 설정해봤는데요. 저같은 경우는 이제 ssh로 접속해서 elastics&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sas-study.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 게시물을 참조하여 gcp 5601 번 포트를 열어주세요!&lt;/p&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2812&quot; data-origin-height=&quot;1766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDBVYA/btr3dcDdBau/CU1tDpz6asBDpEKaRpU3P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDBVYA/btr3dcDdBau/CU1tDpz6asBDpEKaRpU3P1/img.png&quot; data-alt=&quot;키바나 접속 성공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDBVYA/btr3dcDdBau/CU1tDpz6asBDpEKaRpU3P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDBVYA%2Fbtr3dcDdBau%2FCU1tDpz6asBDpEKaRpU3P1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1766&quot; data-origin-width=&quot;2812&quot; data-origin-height=&quot;1766&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;elastic 계정으로 로그인에 성공하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2806&quot; data-origin-height=&quot;1584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IZhcR/btr290RXZPF/EACnEV5jiwsCg71joWuYrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IZhcR/btr290RXZPF/EACnEV5jiwsCg71joWuYrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IZhcR/btr290RXZPF/EACnEV5jiwsCg71joWuYrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIZhcR%2Fbtr290RXZPF%2FEACnEV5jiwsCg71joWuYrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1584&quot; data-origin-width=&quot;2806&quot; data-origin-height=&quot;1584&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;</description>
      <category>ELK/kibana</category>
      <category>엘라스틱 키바나</category>
      <category>엘라스틱서치 키바나 연동</category>
      <category>키바나 다운로드</category>
      <category>키바나 설치</category>
      <category>키바나 시각화 도구</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/498</guid>
      <comments>https://sas-study.tistory.com/498#entry498comment</comments>
      <pubDate>Fri, 10 Mar 2023 19:19:33 +0900</pubDate>
    </item>
    <item>
      <title>[Elasticsearch] 엘라스틱서치 시스템 유저 비밀번호 설정하기.(elasticsearch-setup-passwords)</title>
      <link>https://sas-study.tistory.com/497</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;지난 시간까지 엘라스틱서치를 생짜로 설치해보고 환경설정해보고 클러스터링과 클러스터링 통신간에 tls 를 적용해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://sas-study.tistory.com/492&quot;&gt;엘라스틱서치 설치하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://sas-study.tistory.com/493&quot;&gt;엘라스틱서치 환경설정(Bootstrap Checks)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://sas-study.tistory.com/495&quot;&gt;엘라스틱서치 클러스터 구성하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://sas-study.tistory.com/496&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;엘라스틱서치 클러스터 내 노드 통신에 TLS 적용(Elasticsearch security)&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;이번에는 401 때문에 요청할 수 없는 현재상황에 엘라스틱서치 시스템 유저를 설정하여 클러스터링 현황을 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 필자는 이전 TSL 포스팅에 바로 이어서 작성하고 있기 때문에 TLS가 적용된 상태로 1, 2, 3번 노드가 모두 활성화되어있는 상태입니다. 이 점을 동일시 맞춰놓으시고 진행하시면 어렵지 않게 따라오실 수 있으실 겁니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 엘라스틱서치에서 시스템 유저의 패스워드를 설정할 수 있는 모듈은 elasticsearch-setup-passwords 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 모듈에는 두가지 옵션이 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- auto : 자동으로 패스워드를 설정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- interactive : 두번씩 더블체크하면서 시스템 계정의 비밀번호를 인풋으로 입력받게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 interactive 옵션으로 시스템 비밀번호를 등록하겠습니다. 우선 이전 포스팅에서처럼 공개키의 비밀번호를 그대로 활용하겠습니다.&lt;/p&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번 노드로 ssh 접근한 후 엘라스틱서치 루트 디렉토리로 이동합니다.&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;[1번 노드 서버의 엘라스틱서치 루트 디렉토리에서]&lt;br /&gt;&lt;b&gt;&lt;br /&gt;./bin/elasticsearch-setup-passwords&amp;nbsp;interactive&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfqAxU/btr2D2nTNMS/6hPMSl4zFO7VI3WtXigd11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfqAxU/btr2D2nTNMS/6hPMSl4zFO7VI3WtXigd11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfqAxU/btr2D2nTNMS/6hPMSl4zFO7VI3WtXigd11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfqAxU%2Fbtr2D2nTNMS%2F6hPMSl4zFO7VI3WtXigd11%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;1918&quot; height=&quot;906&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Enter와 Reenter로 더블체크해가면서 패스워드를 설정하도록 합니다. 1번노드에서 설정을 완료하면 현재 클러스터링 되어있는 2번노드와 3번노드에도 그대로 적용이 되어있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엘라스틱서치의 시스템 유저는 elastic 입니다. 이에 기반하여 http request를 curl 명령어로 하나 요청해보겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;[1번 노드 서버의 엘라스틱서치 루트 디렉토리에서]&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;curl&amp;nbsp;&quot;http://localhost:9200/_cat/nodes?v&quot;&amp;nbsp;-u&amp;nbsp;elastic:blackdog&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqDAOr/btr2volxOyh/0D4PgSmfVXsvwlbPSjBkjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqDAOr/btr2volxOyh/0D4PgSmfVXsvwlbPSjBkjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqDAOr/btr2volxOyh/0D4PgSmfVXsvwlbPSjBkjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqDAOr%2Fbtr2volxOyh%2F0D4PgSmfVXsvwlbPSjBkjk%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;1600&quot; height=&quot;172&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;172&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;엘라스틱서치 각 노드간에 클러스터링이 잘 완료되어있는 모습을 authorization을 포함한 http 요청으로 확인할 수 있었습니다.&lt;/p&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>ELK/elasticsearch</category>
      <category>엘라스틱서치 set-up-passwords</category>
      <category>엘라스틱서치 비밀번호</category>
      <category>엘라스틱서치 시스템 유저 비밀번호 설정</category>
      <category>엘라스틱서치 시스템 유저 설정</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/497</guid>
      <comments>https://sas-study.tistory.com/497#entry497comment</comments>
      <pubDate>Wed, 8 Mar 2023 01:41:02 +0900</pubDate>
    </item>
    <item>
      <title>[Elasticsearch] 엘라스틱서치 클러스터 내 노드 통신에 TLS 적용(Elasticsearch security)</title>
      <link>https://sas-study.tistory.com/496</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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://sas-study.tistory.com/492&quot;&gt;엘라스틱서치 설치하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://sas-study.tistory.com/493&quot;&gt;엘라스틱서치 환경설정(Bootstrap Checks)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://sas-study.tistory.com/495&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;현재 엘라스틱서치 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;그로인해서 보안처리가 필요할텐데요. 노드간 통신에 TLS를 적용하여 보다 안전하게 엘라스틱서치 환경을 구성하도록 진행해보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;위에서 말씀드렸다시피 현재의 클러스터 구성은 누구나 9200 포트로 접근하여 엘라스틱서치에 접근할 수 있습니다. 이는 엘라스틱 자체에서 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;b&gt;config/elasticsearch.yml&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;u&gt;저는 1번노드부터 해당 설정을 진행하겠습니다.&lt;/u&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;xpack.security.enabled: true&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 옵션을 활성화하지 않으면 엘라스틱 노드가 기본 및 평가판 디폴드값인 security 설정이 disabled 됩니다. 그래서 이전까지는 별도의 시큐리티가 요구되지 않았던 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 해당 설정을 활성화하고 다시 엘라스틱서치를 실행해보겠습니다. (bin 디렉토리아래 ./elasticsearch 를 실행해줍니다.)&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;ERROR:&amp;nbsp;[1]&amp;nbsp;bootstrap&amp;nbsp;checks&amp;nbsp;failed&lt;br /&gt;[1]:&amp;nbsp;Transport&amp;nbsp;SSL&amp;nbsp;must&amp;nbsp;be&amp;nbsp;enabled&amp;nbsp;if&amp;nbsp;security&amp;nbsp;is&amp;nbsp;enabled&amp;nbsp;on&amp;nbsp;a&amp;nbsp;[basic]&amp;nbsp;license.&amp;nbsp;Please&amp;nbsp;set&amp;nbsp;[xpack.security.transport.ssl.enabled]&amp;nbsp;to&amp;nbsp;[true]&amp;nbsp;or&amp;nbsp;disable&amp;nbsp;security&amp;nbsp;by&amp;nbsp;setting&amp;nbsp;[xpack.security.enabled]&amp;nbsp;to&amp;nbsp;[false]&lt;br /&gt;ERROR:&amp;nbsp;Elasticsearch&amp;nbsp;did&amp;nbsp;not&amp;nbsp;exit&amp;nbsp;normally&amp;nbsp;-&amp;nbsp;check&amp;nbsp;the&amp;nbsp;logs&amp;nbsp;at&amp;nbsp;/home/wsh0821/elasticsearch-7.11.1/logs/blackdog-elastic.log&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그랬더니 실행이되지 않으면서 &lt;b&gt;&lt;u&gt;bootstrap checks&lt;/u&gt;&lt;/b&gt;에 걸려 실행이 중지되었습니다. 내용을 천천히 따라가보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 현재 basic 라이센스(무료 라이센스 인것 같습니다?)로 실행하고 있기 때문에 security 모드가 활성화된다면 transport ssl 설정또한 활성화되어야 한다고 합니다.&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;그리고 설명으로&amp;nbsp; &lt;b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;xpack.security.transport.ssl.enabled&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #666666;&quot;&gt; 설정이 true가 되어야 한다고 합니다. 이를 elasticsearch.yml에 기재해주겠습니다.&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;xpack.security.transport.ssl.enabled&lt;/span&gt;: true&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&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;color: #666666;&quot;&gt;저의 엘라스틱서치 1번노드는 무사히 실행되었고 제 맥북 터미널에서 http요청 하나를 9200번 포트로 요청해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq6HTT/btr2D4lGLBP/YAgUWXpd8UDS2G2R6knWA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq6HTT/btr2D4lGLBP/YAgUWXpd8UDS2G2R6knWA0/img.png&quot; data-alt=&quot;elasticsearch security exception&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq6HTT/btr2D4lGLBP/YAgUWXpd8UDS2G2R6knWA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq6HTT%2Fbtr2D4lGLBP%2FYAgUWXpd8UDS2G2R6knWA0%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;1918&quot; height=&quot;144&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;elasticsearch security exception&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;뭔가 에러같은 json 응답이 돌아왔는데요. 조금 예쁘게 출력해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1678202322783&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;error&quot;: {
    &quot;root_cause&quot;: [
      {
        &quot;type&quot;: &quot;security_exception&quot;,
        &quot;reason&quot;: &quot;missing authentication credentials for REST request [/_cat/nodes?v]&quot;,
        &quot;header&quot;: {
          &quot;WWW-Authenticate&quot;: &quot;Basic realm\u003d\&quot;security\&quot; charset\u003d\&quot;UTF-8\&quot;&quot;
        }
      }
    ],
    &quot;type&quot;: &quot;security_exception&quot;,
    &quot;reason&quot;: &quot;missing authentication credentials for REST request [/_cat/nodes?v]&quot;,
    &quot;header&quot;: {
      &quot;WWW-Authenticate&quot;: &quot;Basic realm\u003d\&quot;security\&quot; charset\u003d\&quot;UTF-8\&quot;&quot;
    }
  },
  &quot;status&quot;: 401
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;status 값을 보니 401 Unauthorized가 응답으로 온 것을 알 수 있습니다. 그리고 reason을 보니 missing authentication credentials 라고 하는데요. 해당 요청에 어떤 인증정보가 없다고 합니다. &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;color: #666666;&quot;&gt;지금 이상태론 엘라스틱서치에 요청조차 할 수 없으니 우선 종료합니다. 그리고 1번노드에 셋팅할 인증정보를 만들어보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;1번노드 인증정보 구성&lt;/span&gt;&lt;/b&gt;&lt;/h4&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: #666666;&quot;&gt;우선 엘라스틱서치는 bin 디렉토리 안에 여러가지 하위 모듈이 존재합니다. 그중에서 우리가 지금 해볼것은 &lt;/span&gt;&lt;b&gt;elasticsearch-certutil &lt;/b&gt;입니다. 해당 모듈로 엘라스틱서치에서 활용할 &lt;u&gt;&lt;b&gt;공개키&lt;/b&gt;&lt;/u&gt;를 만들어줍니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;./bin/elasticsearch-certutil ca&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;해당 명령어를 엘라스틱서치 루트 디렉토리에서 입력하면 아래와 같은 화면이 나타납니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUjQx3/btr2wtGS2eP/uE8cq7R7G1KPwY4jkaEAoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUjQx3/btr2wtGS2eP/uE8cq7R7G1KPwY4jkaEAoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUjQx3/btr2wtGS2eP/uE8cq7R7G1KPwY4jkaEAoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUjQx3%2Fbtr2wtGS2eP%2FuE8cq7R7G1KPwY4jkaEAoK%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;700&quot; height=&quot;740&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 입력은 &lt;u&gt;공개키 파일의 output 이름&lt;/u&gt;을 지정하라고 합니다. 이는 기본(elastic-stack-ca.p12)으로 저장하겠습니다. 엔터.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 입력은 &lt;u&gt;&lt;b&gt;공개키 파일의 패스워드&lt;/b&gt;&lt;/u&gt;를 입력하라고 합니다. 저는 이를 blackdog으로 입력하였습니다. 엔터.&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;엘라스틱서치 루트 디렉토리에서 ls -al 명령어로 디렉토리 목록을 출력해보면 &lt;b&gt;elastic-stack-ca.p12&amp;nbsp;&lt;/b&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;b&gt;대칭키&lt;/b&gt;를 만들 차례입니다. 대칭키를 만들기 위해서는 마찬가지로 elasticsearch-certutil 모듈을 사용합니다. 대칭키를 만드는 명령어는 아래와 같습니다. 그 전에 config 디렉토리 밑에 certs 디렉토리를 하나 생성해주겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1)&lt;br /&gt;엘라스틱서치 루트 디렉토리에서&lt;br /&gt;&lt;b&gt;mkdir config/certs&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;2)&lt;br /&gt;&lt;b&gt;./bin/elasticsearch-certutil cert \&lt;/b&gt;&lt;br /&gt;&lt;b&gt;--ca elastic-stack-ca.p12 \&lt;/b&gt;&lt;br /&gt;&lt;b&gt;--dns blackdog-elastic-1,blackdog-elastic-2,blackdog-elastic-3 \&lt;/b&gt;&lt;br /&gt;&lt;b&gt;--ip 10.178.0.12,10.178.0.13,10.178.0.14 \&lt;/b&gt;&lt;br /&gt;&lt;b&gt;--out config/certs/blackdog-cluster.p12&lt;/b&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;b&gt;cert&lt;/b&gt; 명령어 옵션으로 ca 공개키 파일 경로를 입력합니다. 현재 명령어를 입력하는 위치는 엘라스틱서치의 루트 디렉토리이므로 경로를 주의해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;dns&lt;/b&gt;는 tls를 적용할 클러스터링 대상 노드 호스트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ip&lt;/b&gt;는 gcp 내부에서 관리하는 ip로 설정해주었습니다. 각각 1번노드 내부ip, 2번노드 내부ip, 3번노드 내부ip 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;out&lt;/b&gt;은 출력으로 config 디렉토리 -&amp;gt; certs 디렉토리를 하나 생성해주고 그 하위에 출력하도록 합니다.&lt;/p&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 data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Enter&amp;nbsp;password&amp;nbsp;for&amp;nbsp;CA&amp;nbsp;(elastic-stack-ca.p12)&amp;nbsp;:&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Enter&amp;nbsp;password&amp;nbsp;for&amp;nbsp;blackdog-cluster.p12&amp;nbsp;:&lt;/b&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;b&gt;&lt;u&gt;공개키&lt;/u&gt;의 비밀번호&lt;/b&gt;를 입력받고, 두번째 입력은 생성할 &lt;b&gt;&lt;u&gt;대칭키&lt;/u&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;이제 config 디렉토리 하위의 certs 디렉토리 내부 목록을 출력해보면 blackdog-cluster.p12 파일이 있는 것을 확인하실 수 있을겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQneyK/btr2CvqCusD/L99VXDClJJqKBgXZLcESmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQneyK/btr2CvqCusD/L99VXDClJJqKBgXZLcESmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQneyK/btr2CvqCusD/L99VXDClJJqKBgXZLcESmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQneyK%2Fbtr2CvqCusD%2FL99VXDClJJqKBgXZLcESmK%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;962&quot; height=&quot;60&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1번노드 대칭키 정보 저장&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에까지 대칭키 정보를 만들어서 엘라스틱서치 루트디렉토리/config/certs 위치에 생성되었는데요. 이를 엘라스틱서치 설정파일에 기재해주겠습니다.&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;&lt;b&gt;xpack.security.transport.ssl.keystore.path: certs/blackdog-cluster.p12&lt;/b&gt;&lt;br /&gt;&lt;b&gt;xpack.security.transport.ssl.truststore.path: certs/blackdog-cluster.p12&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 내용을 elasticsearch.yml 파일에 기재해줍니다.&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;그리고 설정파일에 비밀번호를 직접 노출시키는 형태가 아닌 keystore라는 저장소 모듈에 등록해야 합니다. 따라서 bin 디렉토리 하위에 있는 elasticsearch-keystore 모듈을 사용하여 keystore를 먼저 하나 생성해보겠습니다.&lt;/p&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;style3&quot;&gt;&lt;b&gt;./bin/elasticsearch-keystore create&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음 명령어로 keystore.seed가 출력되는지 확인해야합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;./bin/elasticsearch-keystore list&lt;/b&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;패스워드를 저장할 keystore를 생성했으니 이제 패스워드를 저장해야하는데 어떤 패스워드인지 의문이 있을 수 있습니다.&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;xpack.security.transport.ssl.keystore.path: certs/blackdog-cluster.p12&lt;br /&gt;xpack.security.transport.ssl.truststore.path: certs/blackdog-cluster.p12&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래의 명령어를 진행해주세요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;./bin/elasticsearch-keystore add xpack.security.transport.ssl.keystore.secure_password&lt;/b&gt;&lt;br /&gt;&lt;b&gt;./bin/elasticsearch-keystore add xpack.security.transport.ssl.truststore.secure_password&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 각각의 설정에 맞는 패스워드를 입력받습니다. 저는 위에서부터 말씀드렸듯이 공개키 비밀번호와 동일하게 blackdog으로 진행하였습니다. 상황에 맞게 잘 등록하시기 바랍니다.(여기는 오타가 나도 계속해서 overwrite 할 수 있습니다. 오타가 발생했다면 다시 입력해서 overwrite 하시면 됩니다.)&lt;/p&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;b&gt;./bin/elasticsearch-keystore list &lt;/b&gt;명령어를 통해 아래와 같이 세가지 내용이 출력되는지 확인해주시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMip4w/btr2ufvBz7u/kfy9myIhDnjHYnC7HokIkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMip4w/btr2ufvBz7u/kfy9myIhDnjHYnC7HokIkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMip4w/btr2ufvBz7u/kfy9myIhDnjHYnC7HokIkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMip4w%2Fbtr2ufvBz7u%2Fkfy9myIhDnjHYnC7HokIkk%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;1190&quot; height=&quot;140&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;140&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2, 3번 노드 동일한 설정 추가.&lt;/b&gt;&lt;/h4&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;2, 3번노드와 함께 실행하지 않아서 30초동안 디스커버리 과정을 통해서 시간이 오래걸리긴 했지만 아까처럼 bootstrap checks에 걸리지도 않고 무사히 실행은 되었습니다. 그렇지만 계속해서 디스커버리를 시도하므로 2, 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;scp 명령어로 제 로컬 pc에 blackdog-cluster.p12 를 다운로드 받고 이를 2, 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;우선 동일한 대칭키 사용을 위해서 1번노드에서 제 로컬 pc로 대칭키를 다운로드 받겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;[로컬 PC에서 명령]&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;scp -i ~/.ssh/blackdog-elasticsearch wsh0821@{1번노드아이피}:/home/wsh0821/elasticsearch-7.11.1/config/certs/blackdog-cluster.p12 ./&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 전에 2, 3번 노드 서버에서 엘라스틱서치 루트 디렉토리의 config 디렉토리 하위에 certs 디렉토리를 생성해줍니다.&lt;/p&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, 3번 노드로 전송시켜줍니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[로컬 PC에서 명령]&lt;b&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;scp -i ~/.ssh/blackdog-elasticsearch ./blackdog-cluster.p12 wsh0821@{2번노드아이피}:/home/wsh0821/elasticsearch-7.11.1/config/certs/blackdog-cluster.p12&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;scp -i ~/.ssh/blackdog-elasticsearch ./blackdog-cluster.p12 wsh0821@{3번노드아이피}:/home/wsh0821/elasticsearch-7.11.1/config/certs/blackdog-cluster.p12&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 certs 디렉토리 하위에 대칭키가 잘 다운로드됐는지 확인해주시고 1번 노드처럼 elasticsearch.yml 파일에 이의 경로를 기재해주겠습니다.&lt;/p&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,3번 노드의 elasticsearch.yml에 아래의 내용을 복붙해줍니다. (security와 tls 활성화 설정도 추가해줘야합니다.)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[2, 3번 노드의 config/elasticsearch.yml 파일]&lt;b&gt;&lt;br /&gt;xpack.security.enabled:&amp;nbsp;true&lt;/b&gt;&lt;br /&gt;&lt;b&gt;xpack.security.transport.ssl.enabled:&amp;nbsp;true&lt;/b&gt;&lt;br /&gt;&lt;b&gt;xpack.security.transport.ssl.keystore.path:&amp;nbsp;certs/blackdog-cluster.p12&lt;/b&gt;&lt;br /&gt;&lt;b&gt;xpack.security.transport.ssl.truststore.path:&amp;nbsp;certs/blackdog-cluster.p12&lt;/b&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;그리고 keystore 설정도 빠르게 해줍니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[2, 3번 노드의 엘라스틱서치 루트 디렉토리]&lt;br /&gt;&lt;b&gt;./bin/elasticsearch-keystore create&lt;/b&gt;&lt;br /&gt;&lt;b&gt;./bin/elasticsearch-keystore add xpack.security.transport.ssl.keystore.secure_password&lt;/b&gt;&lt;br /&gt;&lt;b&gt;./bin/elasticsearch-keystore add xpack.security.transport.ssl.truststore.secure_password&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;아래의 명령어로 잘 등록됐는지 확인합니다.&lt;br /&gt;./bin/elasticsearch-keystore list&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엘라스틱서치 실행전 1, 2, 3번 노드의 모든 data 디렉토리를 삭제하고 진행하겠습니다. 기존 실행이력 때문인지 원활하게 클러스터링이 되지 않을 때가 있습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 실행이력을 가지고 있는 data 디렉토리를 삭제하고나니 클러스터링이 잘 되는 것을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음시간에는 유저를 등록하여 실제 클러스터링이 잘 진행됐는지 curl로 확인해보겠습니다.&lt;/p&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>ELK/elasticsearch</category>
      <category>엘라스틱서치 certutil 공개키 대칭키</category>
      <category>엘라스틱서치 keystore</category>
      <category>엘라스틱서치 security 활성화</category>
      <category>엘라스틱서치 공개키 대칭키</category>
      <category>엘라스틱서치 클러스터링 노드 tls 적용</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/496</guid>
      <comments>https://sas-study.tistory.com/496#entry496comment</comments>
      <pubDate>Wed, 8 Mar 2023 01:26:45 +0900</pubDate>
    </item>
    <item>
      <title>[Elasticsearch] 엘라스틱서치 클러스터 구성해보기</title>
      <link>https://sas-study.tistory.com/495</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;지난 포스팅에서는 엘라스틱서치를 설치하고 환경설정하여 Bootstrap Checks 까지 넘어가보는 예제를 구성해보았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://sas-study.tistory.com/492&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;a href=&quot;https://sas-study.tistory.com/493&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;엘라스틱서치 환경설정(Bootstrap Checks)&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;이번 포스팅에서는 구성한 Bootstrap Checks를 통과한 하나의 노드서버를 기준으로&amp;nbsp; 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번 노드를&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/494&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/494&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1678072900409&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;[GCP] VM 인스턴스 이미지 만들기(머신 복제)&quot; data-og-description=&quot;안녕하세요. 오늘은 VM 인스턴스의 머신 이미지를 복사하여 같은 환경의 VM 인스턴스를 만드는 과정을 공유하고자 합니다. 우선 제가 이 행위에 대해서 왜 하고 왜 필요한지에 대해서 먼저 설명&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/494&quot; data-og-url=&quot;https://sas-study.tistory.com/494&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iapjW/hyRQlj6M2i/giCmoddoq74C1KHP8uMx6K/img.png?width=800&amp;amp;height=368&amp;amp;face=0_0_800_368,https://scrap.kakaocdn.net/dn/oNLAS/hyRQkZOMkF/KHW53kTelAs9URZThrpCxK/img.png?width=800&amp;amp;height=368&amp;amp;face=0_0_800_368,https://scrap.kakaocdn.net/dn/6NnjI/hyRQo8XspS/OEKhmdHujDTzk6YQlHIZ9K/img.png?width=2268&amp;amp;height=422&amp;amp;face=0_0_2268_422&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/494&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/494&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iapjW/hyRQlj6M2i/giCmoddoq74C1KHP8uMx6K/img.png?width=800&amp;amp;height=368&amp;amp;face=0_0_800_368,https://scrap.kakaocdn.net/dn/oNLAS/hyRQkZOMkF/KHW53kTelAs9URZThrpCxK/img.png?width=800&amp;amp;height=368&amp;amp;face=0_0_800_368,https://scrap.kakaocdn.net/dn/6NnjI/hyRQo8XspS/OEKhmdHujDTzk6YQlHIZ9K/img.png?width=2268&amp;amp;height=422&amp;amp;face=0_0_2268_422');&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;[GCP] VM 인스턴스 이미지 만들기(머신 복제)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 오늘은 VM 인스턴스의 머신 이미지를 복사하여 같은 환경의 VM 인스턴스를 만드는 과정을 공유하고자 합니다. 우선 제가 이 행위에 대해서 왜 하고 왜 필요한지에 대해서 먼저 설명&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sas-study.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;아래의 vm 인스턴스 혹은 aws로 진행하시는 경우 aws에서 제공하는 스냅샷 기능을 통해서 복사한 후 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;해당 서버가 준비되었다면 우선 세 서버를 접속합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 세 노드 서버의 config 디렉토리 아래에 있는 elasticsearch.yml 파일이 1번 노드와 동일하게 적혀져 있을 겁니다. 이 부분을 각각 노드서버에 맞게 수정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2862&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8pKaH/btr2rDVnG9H/ZVva6qeiTRx3nUIPksiyh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8pKaH/btr2rDVnG9H/ZVva6qeiTRx3nUIPksiyh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8pKaH/btr2rDVnG9H/ZVva6qeiTRx3nUIPksiyh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8pKaH%2Fbtr2rDVnG9H%2FZVva6qeiTRx3nUIPksiyh0%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;2862&quot; height=&quot;716&quot; data-origin-width=&quot;2862&quot; data-origin-height=&quot;716&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;b&gt;cluster.name&lt;/b&gt; : 해당 속성으로 클러스터 명이 설정되며 각 노드간의 클러스터 가입이 해당 값이 같은 값을 기준으로 클러스터링 됩니다. 각 노드간 동일한 값으로 설정해야하니 따로 건드리지는 않겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;node.name&lt;/b&gt; : 각 노드의 이름입니다. 각 노드간 자신들이 통신해야될 노드명과 호스트는 알아야만 통신할 수 있어서 각각 넘버링에 맞게 정의해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;network.host&lt;/b&gt; : 해당 값은 기존의 1번 노드의 내부IP 값으로 설정되었을텐데요. 이를 환경변수를 활용해서 _local_, _site_로 수정해줍니다. _local_은 내부 루프백 주소(localhost/127.0.0.1) 과 같습니다. _site_는 1번 노드는 기존 gcp 내부 IP 값과 동일할테지만 각각 2,3번 노드는 자신들이 설치된 노드 서버의 내부IP 값이 설정되게 됩니다. &lt;s&gt;(머신 이미지 만들때 이부분을 해놓을껄.. 하고 후회중입니다.)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;discovery.seed_hosts&lt;/b&gt; : 각 노드가 클러스터를 구성하기 위해서 마스터 노드를 선출하기 위해 노드들을 탐색하는 과정을 디스커버리라고 합니다. 그때 대상 호스트를 알아야 하기 때문에 여기서는 1,2,3번 노드의 host 명을 적어주겠습니다. 근데 여기서 host는 서버 주소여야하는데 이 다음에 호스트 파일을 수정하여 각각 IP를 등록하여 해당 문자열을 인식했다고 생각해주시면 되겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;cluster.initial_master_nodes&lt;/b&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;b&gt;호스트 파일 수정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 위에서 discovery.seed_hosts 설정에서 호스트 ip 주소가 아닌 문자열이 들어간 것을 확인할 수 있습니다. gcp에서 정해준 host로 찾아간다고 생각하실 수 있겠지만 gcp 이전에는 해당 서버는 단순 물리 서버이기 때문에 기똥차게 무언가를 찾아내거나 gcp에서 해당 기능을 제공한다고 생객해서는 안됩니다. 그래서 host 파일을 수정해서 우리가 정해놓은 호스트명에 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;호스트 파일의 경로는&amp;nbsp; /etc/hosts 이고 해당 파일은 관리자 권한이기 때문에 sudo 명령어를 사용합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;sudo&amp;nbsp;vi&amp;nbsp;/etc/hosts&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파일에 아래와 같이 기재해주겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;10.178.0.9&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;blackdog-elastic-1&lt;br /&gt;10.178.0.10&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;blackdog-elastic-2&lt;br /&gt;10.178.0.11&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;blackdog-elastic-3&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 노드의 ip(10.178.0.9) 를 blackdog-elastic-1로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 노드의 ip(10.178.0.10) 를 blackdog-elastic-2로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번 노드의 ip(10.178.0.11) 를 blackdog-elastic-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;이는 1번 노드에서만 해당하기 때문에 이를 2번, 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;이렇게하면 노드간의 통신할때, curl 명령어로 예를 들자면&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;curl 10.178.0.9:9200&lt;/b&gt;&lt;br /&gt;&lt;b&gt;curl blackdog-elastic-1:9200&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두가지를 모두 인식하게 됩니다. 따라서 discovery.seed_hosts 속성에서 지정해놓은 값은 단순 host 앨리야스라고 생각하시면 될것 같습니다.&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;b&gt;방화벽 추가 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 노드간 클러스터링을 위해서 설정은 마무리가 되었습니다. 하지만 엘라스틱서치 관점에서의 설정이고 물리적 서버 관점에서는 아직 엘라스틱서치간의 통신이 되지 않습니다. 왜냐하면 노드간 내부통신을 할때 사용하는 9300 번 포트를 gcp 방화벽에서 설정해주지 않았기 때문입니다.&lt;/p&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/3번 노드는 외부와의 9200 포트를 닫아놓고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 9300 번 포트는 각 노드의 서버에서만 통신이 되며 외부와의 통신은 단절되도록 셋팅하겠습니다.(개인 맥북으로는 9300으로 요청이 안되도록 하겠음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 gcp의 vm 인스턴스에서 방화벽 규칙 추가로 돌아가보겠습니다.&lt;/p&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;blackdog-elastic-internal&lt;/b&gt;로 지정해주겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcDjaN/btr2r5qMx2i/4oF3bWLPYwGEFX7bU2KnWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcDjaN/btr2r5qMx2i/4oF3bWLPYwGEFX7bU2KnWk/img.png&quot; data-alt=&quot;방화벽 정보&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcDjaN/btr2r5qMx2i/4oF3bWLPYwGEFX7bU2KnWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcDjaN%2Fbtr2r5qMx2i%2F4oF3bWLPYwGEFX7bU2KnWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;1090&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;1090&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;이제 여기가 제일 중요한데요. 저는 대상을 지정된 대상 태그(태그가 붙어있는 곳에 방화벽을 적용하겠다는 것.)를 이용하여 방화벽규칙과 동일한 태그를 추가한 서버만이 9200, 9300 번 포트를 오픈하도록 합니다.&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;소스태그 : 요청을 받을 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;그래서 1번, 2번, 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;태그조건때문에 1번 노드는 9200, 9300 번 포트를 오픈하면서 2/3번 노드 서버에서만 해당 포트의 트래픽을 허용합니다. 2번은 1/3번 3번은 1/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번은 9200 포트를 외부 클라이언트와 통신용으로 열어두었기 때문에 9200 포트는 모두에게 public 하게 열려있게 됩니다. 이를 잘 이해하셔야 합니다.&lt;/p&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;b&gt;방화벽 적용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 인스턴스 수정으로 접근하여 네트워크 태그를 추가해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csUwPK/btr1URPdfDo/Oxd3tKEKW8dzQvhXeFQsvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csUwPK/btr1URPdfDo/Oxd3tKEKW8dzQvhXeFQsvK/img.png&quot; data-alt=&quot;1번 노드의 vm 인스턴스 네트워크 태그 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csUwPK/btr1URPdfDo/Oxd3tKEKW8dzQvhXeFQsvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsUwPK%2Fbtr1URPdfDo%2FOxd3tKEKW8dzQvhXeFQsvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;328&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1번 노드의 vm 인스턴스 네트워크 태그 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgpDSu/btr1Wybay5q/IL7sRR5ftrwMQavc14ozLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgpDSu/btr1Wybay5q/IL7sRR5ftrwMQavc14ozLk/img.png&quot; data-alt=&quot;2번 노드의 vm 인스턴스 네트워크 태그 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgpDSu/btr1Wybay5q/IL7sRR5ftrwMQavc14ozLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgpDSu%2Fbtr1Wybay5q%2FIL7sRR5ftrwMQavc14ozLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;300&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2번 노드의 vm 인스턴스 네트워크 태그 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6Ahxz/btr1WmOWggd/CRkbdw1rJKKs4KZ2QKFV1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6Ahxz/btr1WmOWggd/CRkbdw1rJKKs4KZ2QKFV1K/img.png&quot; data-alt=&quot;3번 노드의 vm 인스턴스 네트워크 태그 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6Ahxz/btr1WmOWggd/CRkbdw1rJKKs4KZ2QKFV1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6Ahxz%2Fbtr1WmOWggd%2FCRkbdw1rJKKs4KZ2QKFV1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;300&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;3번 노드의 vm 인스턴스 네트워크 태그 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 방화벽을 모두 적용해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;클러스터 구성 확인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 노드서버에 접근하여 엘라스틱서치를 실행해주겠습니다. 우선 기존 노드에 실행이력이 있는 data 디렉토리를 삭제해주겠습니다. 해당 data 디렉토리에 있는 실행이력때문에 클러스터링이 잘 진행되지 않았기에 혹시나 원하시는 결과가 안나오신다면 엘라스틱서치 루트 디렉토리에 있는 data 디렉토리를 삭제해주세요.&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;&lt;b&gt;rm -rf data&lt;/b&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;bin 폴더 아래 elasticsearch 를 실행하였고 문제없이 started가 되어있습니다. 로그로 클러스터가 구성된 부분을 확인할 수 있겠지만 조금 많기에 curl 명령어로 1번 노드에 요청을 하나 넣어보겠습니다.&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;&lt;b&gt;curl &quot;http://{1번노드IP}:9200/_cat/nodes?v&quot;&lt;/b&gt;&lt;/blockquote&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;1526&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwBvxD/btr2aIwFS45/QHkBqeBN5tSZklmUzJIZ7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwBvxD/btr2aIwFS45/QHkBqeBN5tSZklmUzJIZ7k/img.png&quot; data-alt=&quot;노드간 클러스터 구성 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwBvxD/btr2aIwFS45/QHkBqeBN5tSZklmUzJIZ7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwBvxD%2Fbtr2aIwFS45%2FQHkBqeBN5tSZklmUzJIZ7k%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;1526&quot; height=&quot;176&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;176&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;디스커버리를 원활하게 수행한 것을 확인되고 2번 노드가 마스터 노드로 선출된 것을 확인하실 수 있으실겁니다. 또한 name쪽도 제가 작성한 노드의 이름으로 설정된 것을 확인할 수있었습니다.&lt;/p&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>ELK/elasticsearch</category>
      <category>configuration elasticsearch clsuter</category>
      <category>elasticsearch cluster</category>
      <category>엘라스틱서치 클러스터</category>
      <category>엘라스틱서치 클러스터 구성</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/495</guid>
      <comments>https://sas-study.tistory.com/495#entry495comment</comments>
      <pubDate>Mon, 6 Mar 2023 12:58:13 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] VM 인스턴스 이미지 만들기(머신 복제)</title>
      <link>https://sas-study.tistory.com/494</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;오늘은 VM 인스턴스의 머신 이미지를 복사하여 같은 환경의 VM 인스턴스를 만드는 과정을 공유하고자 합니다.&lt;/p&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;저는 현재 엘라스틱서치에 대한 포스팅과 동시에 Google Cloud 서비스에 대한 사용법도 함께 공유하고 있습니다. 이에 따라 엘라스틱서치 환경을 구성하는데 필요한 n대의 서버가 필요한데요.&lt;/p&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개의 노드를 구성하는데 필요한 환경을 모두 구성해놓은 상태입니다. 따라서 저는 처음 엘라스틱서치를 작업한 서버의 환경과 그대로 일치하는 n개의 vm 인스턴스 머신이 필요한 상황입니다.&lt;/p&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;이럴때 필요한게 바로 대량&lt;b&gt;복사&lt;/b&gt;의 기능인데요. 이게 바로 VM 인스턴스 머신 이지미를 통해 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VM 인스턴스 머신 이미지는 특정 VM 인스턴스의 &lt;b&gt;스냅샷&lt;/b&gt;을 하나 생성하여 이미지화하고 VM 인스턴스를 신규로 생성할 때 해당 이미지를 불러오는 형태로 스냅샷 대상 &lt;u&gt;&lt;b&gt;VM 인스턴스와 스펙이 같은 머신을 생성할 수 있도록 합니다.&lt;/b&gt;&lt;/u&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;VM 인스턴스 이미지 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 VM 인스턴스 목록에서 스냅샷을 찍을 vm 인스턴스의 메뉴를 클릭합니다. 그 중에서 &lt;b&gt;새 머신 이미지 만들기&lt;/b&gt;를 선택해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2156&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca63FZ/btr2aIJ3T1P/Q6L3wKgwC7BejQYeSx05sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca63FZ/btr2aIJ3T1P/Q6L3wKgwC7BejQYeSx05sK/img.png&quot; data-alt=&quot;vm 인스턴스 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca63FZ/btr2aIJ3T1P/Q6L3wKgwC7BejQYeSx05sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca63FZ%2Fbtr2aIJ3T1P%2FQ6L3wKgwC7BejQYeSx05sK%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;700&quot; height=&quot;323&quot; data-origin-width=&quot;2156&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;vm 인스턴스 목록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 머신 이미지를 만드는 화면으로 이동할텐데요. GCP에서 안내해주는대로 따라가보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/24AV8/btr1QxDGfZS/drgKPaQnKvTVP3IkCwVSjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/24AV8/btr1QxDGfZS/drgKPaQnKvTVP3IkCwVSjk/img.png&quot; data-alt=&quot;머신 이미지 만들기_1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/24AV8/btr1QxDGfZS/drgKPaQnKvTVP3IkCwVSjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F24AV8%2Fbtr1QxDGfZS%2FdrgKPaQnKvTVP3IkCwVSjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;754&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;머신 이미지 만들기_1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 머신 이미지의 이름을 지정해줍니다. 아무래도 추상적인 이름보다야 조금 더 구체적인 이름이 좀더 좋을 것 같아보입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명의 경우는 이름에서 서술하지 못한 좀더 상세한 내용을 썼습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스 VM 인스턴스는 아마 지정되어있을텐데요. 스냅샷 이미지를 만들 vm 인스턴스를 지정하는 것이니 위에서부터 따라오신분이라면 수정할 필요는 없어보입니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYTTdw/btr1Wya2wC4/VGEGJ8iAPaviH9R7Wu9hBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYTTdw/btr1Wya2wC4/VGEGJ8iAPaviH9R7Wu9hBK/img.png&quot; data-alt=&quot;머신 이미지 만들기_2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYTTdw/btr1Wya2wC4/VGEGJ8iAPaviH9R7Wu9hBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYTTdw%2Fbtr1Wya2wC4%2FVGEGJ8iAPaviH9R7Wu9hBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;360&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;머신 이미지 만들기_2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위치는 기존 VM 인스턴스와 동일한 리전으로 선택해주겠습니다. 저는 서울에다 만들었기 때문에 리전을 서울로 지정해주었습니다.&lt;/p&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;조금의 시간동안 gcp 화면에서는 이미지를 생성하고 있다는 로딩표시가 보일텐데요. 해당 과정이 모두 마무리가 된다면 이미지가 생성된 것을 확인하실 수 있으실겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;머신 이미지로 VM 인스턴스 생성하기.&lt;/b&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;2268&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsLcqp/btr1URO7fr1/83LdOG8kbQgtpUojZp6EjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsLcqp/btr1URO7fr1/83LdOG8kbQgtpUojZp6EjK/img.png&quot; data-alt=&quot;머신이미지로 인스턴스 만들기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsLcqp/btr1URO7fr1/83LdOG8kbQgtpUojZp6EjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsLcqp%2Fbtr1URO7fr1%2F83LdOG8kbQgtpUojZp6EjK%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;2268&quot; height=&quot;422&quot; data-origin-width=&quot;2268&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;머신이미지로 인스턴스 만들기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dV6qkx/btr133Vf1TP/c6d14yIKxBrUEkkjN3Vt7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dV6qkx/btr133Vf1TP/c6d14yIKxBrUEkkjN3Vt7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dV6qkx/btr133Vf1TP/c6d14yIKxBrUEkkjN3Vt7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdV6qkx%2Fbtr133Vf1TP%2Fc6d14yIKxBrUEkkjN3Vt7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;569&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스 머신 이미지는 위에서 생성한 blackdog-elastic으로 잡혀있는 것이 확인되고 이름도 blackdog-elastic-2로 맞춰줍니다.(사실 이건구글에서 맞춰준건데 신기한데요??)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 리전과 영역이 아마 머신이미지가 저장된 곳과 다르게 설정되어 있을겁니다. 이를 리전 서울 영역은 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;또한 운영체제를 설정하는 곳은 해당 화면에서 나타나지 않습니다. 왜냐하면 머신이미지를 통해 스냅샷을 떠놓은 상태기 때문에 해당 기능으로 만드는 vm 인스턴스는 머신 이미지와 운영체제가 같을 수 밖에 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 만들기를 클릭해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTPi15/btr18EgzBgR/8mtliMkLIGL5B15WkYb7H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTPi15/btr18EgzBgR/8mtliMkLIGL5B15WkYb7H1/img.png&quot; data-alt=&quot;vm 인스턴스 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTPi15/btr18EgzBgR/8mtliMkLIGL5B15WkYb7H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTPi15%2Fbtr18EgzBgR%2F8mtliMkLIGL5B15WkYb7H1%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;1880&quot; height=&quot;394&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;vm 인스턴스 목록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;blackdog-elastic-2가 생성된 것을 확인할 수 있고 이제 각각의 머신에 ssh로 접근하여 비교해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2862&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Okjfs/btr1XyhxgRb/V2Z9meIkcxzEASJfEoWC41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Okjfs/btr1XyhxgRb/V2Z9meIkcxzEASJfEoWC41/img.png&quot; data-alt=&quot;두 머신에 모두 elasticsearch 7.11.1 버전이 설치됨.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Okjfs/btr1XyhxgRb/V2Z9meIkcxzEASJfEoWC41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOkjfs%2Fbtr1XyhxgRb%2FV2Z9meIkcxzEASJfEoWC41%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;2862&quot; height=&quot;718&quot; data-origin-width=&quot;2862&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;두 머신에 모두 elasticsearch 7.11.1 버전이 설치됨.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 머신에 다 elasticsearch-7.11.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;감사합니다.&lt;/p&gt;</description>
      <category>GCP</category>
      <category>GCP VM 이미지</category>
      <category>gcp 머신 이미지</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/494</guid>
      <comments>https://sas-study.tistory.com/494#entry494comment</comments>
      <pubDate>Mon, 6 Mar 2023 11:28:21 +0900</pubDate>
    </item>
    <item>
      <title>[Elasticsearch] 엘라스틱 서치 환경설정(Bootstrap Checks)</title>
      <link>https://sas-study.tistory.com/493</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;지난 포스팅에서는 엘라스틱서치를 설치하여 curl 명령어로 내부 루프백 주소 키워드인 localhost로 요청하여 응답을 받아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/492&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/492&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677912641437&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;[Elasticsearch] 엘라스틱 서치 설치하기&quot; data-og-description=&quot;안녕하세요. 이전 포스팅이 대부분 Google Cloud VM 인스턴스에 관한 내용이었을텐데요. 포스팅도 포스팅이지만 사실은 엘라스틱서치를 학습하기 위해서 (로컬에 해도됩니다만 지저분해지는걸 싫&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/492&quot; data-og-url=&quot;https://sas-study.tistory.com/492&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ZFle3/hyROXcP1Zt/VSr4dohgnCoIVDrhx3sgs0/img.png?width=800&amp;amp;height=173&amp;amp;face=0_0_800_173,https://scrap.kakaocdn.net/dn/bIVQNU/hyROLXGM3D/XzaEClhY9UrJkkWtYWkfNk/img.png?width=800&amp;amp;height=173&amp;amp;face=0_0_800_173,https://scrap.kakaocdn.net/dn/xP2sr/hyRQisPyED/ikMPIzLxxW8eykpuZhkdZ0/img.png?width=2534&amp;amp;height=548&amp;amp;face=0_0_2534_548&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/492&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/492&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ZFle3/hyROXcP1Zt/VSr4dohgnCoIVDrhx3sgs0/img.png?width=800&amp;amp;height=173&amp;amp;face=0_0_800_173,https://scrap.kakaocdn.net/dn/bIVQNU/hyROLXGM3D/XzaEClhY9UrJkkWtYWkfNk/img.png?width=800&amp;amp;height=173&amp;amp;face=0_0_800_173,https://scrap.kakaocdn.net/dn/xP2sr/hyRQisPyED/ikMPIzLxxW8eykpuZhkdZ0/img.png?width=2534&amp;amp;height=548&amp;amp;face=0_0_2534_548');&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;[Elasticsearch] 엘라스틱 서치 설치하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 이전 포스팅이 대부분 Google Cloud VM 인스턴스에 관한 내용이었을텐데요. 포스팅도 포스팅이지만 사실은 엘라스틱서치를 학습하기 위해서 (로컬에 해도됩니다만 지저분해지는걸 싫&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sas-study.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;우선 위의 포스팅에서는 엘라스틱서치가 Development 모드로 동작하여 외부에서의 요청은 받지 못하는 상태입니다. 이번에는 외부에서 요청을 받을 수 있도록 설정을 해보고 그로 인해 진행하는 Bootstrap cheks에서 발생하는 실행 문제를 해결해나가보도록 하겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Bootstrap Checks를 하는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Bootstap Checks를 하는 이유는 아래의 페이지에 잘 설명되어있습니다. 저는 이를 간단히 요약만하고 환경설정에 집중하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/bootstrap-checks.html#bootstrap-checks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/bootstrap-checks.html#bootstrap-checks&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677913284908&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;Bootstrap Checks | Elasticsearch Guide [7.11] | Elastic&quot; data-og-description=&quot;Collectively, we have a lot of experience with users suffering unexpected issues because they have not configured important settings. In previous versions of Elasticsearch, misconfiguration of some of these settings were logged as warnings. Understandably,&quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/bootstrap-checks.html#bootstrap-checks&quot; data-og-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/bootstrap-checks.html#bootstrap-checks&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/bootstrap-checks.html#bootstrap-checks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/bootstrap-checks.html#bootstrap-checks&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;Bootstrap Checks | Elasticsearch Guide [7.11] | Elastic&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Collectively, we have a lot of experience with users suffering unexpected issues because they have not configured important settings. In previous versions of Elasticsearch, misconfiguration of some of these settings were logged as warnings. Understandably,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이전 버전의 Elasticsearch에서는 중요한 설정을 구성하지 않아 예기치 않은 문제가 발생하는 사용자가 많았습니다. 그래서 Elasticsearch는 Bootstrap Checks를 제공하여 중요한 설정이 구성되지 않았을 때 Elasticsearch가 시작되지 않도록 보장합니다. 부트스트랩 체크는 Elasticsearch 및 시스템 설정을 검사하고 안전한 값과 비교하여 Elasticsearch 운영에 안전하지 않은 경우 경고 메시지를 로그에 남기거나 프로덕션 모드에서는 Elasticsearch가 시작되지 않도록합니다. 일부 부트스트랩 체크는 호환되지 않는 설정으로 Elasticsearch가 실행되지 않도록 항상 강제로 적용됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;쉽게 풀자면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Elasticsearch를 운영하는 측에서는 이전에 elasticsearch 사용자로부터 어떠한 시스템 환경 이슈를 로그형태로써 보여줬지만 이를 알아채지 못하는 사용자로 인해서 운영환경에서 크리티컬한 이슈들을 마주쳤고 근본적으로 시스템 환경 이슈를 마주치지 않을만한 그들만의 허용 가능한 스펙을 정해 이를 사용자의 elasticsearch 실행 환경과 비교하는 로직을 elasticsearch를 production 모드로 실행할 때 체크하도록 하고 이를 통과해야만 실행이 가능하도록 하였다는 것 같습니다.&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 내용을 토대로 알 수 있는 것은 현재 설치된 elasticsearch는 development 모드로 실행중이니 외부에서 네트워크 호출을 통해 호출할 수 없습니다. 그런데 이를 production 모드로 변경하면 bootstrap checks를 통해 실행을 할 수 없는 상태가 됩니다. 따라서 이를 실행 가능한 상태로 서버 시스템 설정을 변경해주어야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 좀 내용이 많이 복잡하니 우선 production 모드로 실행되도록 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 elasticsearch 가 설치된 서버에 접속하여 elasticsearch가 설치된 디렉토리로 접근해보겠습니다. 그 중 config 폴더 안에 elasticsearch.yml 이라는 파일을 한번 열어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677914138643&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ======================== Elasticsearch Configuration =========================
#
# NOTE: Elasticsearch comes with reasonable defaults for most settings.
#       Before you set out to tweak and tune the configuration, make sure you
#       understand what are you trying to accomplish and the consequences.
#
# The primary way of configuring a node is via this file. This template lists
# the most important settings you may want to configure for a production cluster.
#
# Please consult the documentation for further information on configuration options:
# https://www.elastic.co/guide/en/elasticsearch/reference/index.html
#
# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
#
#cluster.name: my-application
#
# ------------------------------------ Node ------------------------------------
#
# Use a descriptive name for the node:
#
#node.name: node-1
#
# Add custom attributes to the node:
#
#node.attr.rack: r1
#
# ----------------------------------- Paths ------------------------------------
#
# Path to directory where to store the data (separate multiple locations by comma):
#
#path.data: /path/to/data
#
# Path to log files:
#
#path.logs: /path/to/logs
#
# ----------------------------------- Memory -----------------------------------
#
# Lock the memory on startup:
#
#bootstrap.memory_lock: true
#
# Make sure that the heap size is set to about half the memory available
# on the system and that the owner of the process is allowed to use this
# limit.
#
# Elasticsearch performs poorly when the system is swapping the memory.
#
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
#
#network.host: 192.168.0.1
#
# Set a custom port for HTTP:
#
#http.port: 9200
#
# For more information, consult the network module documentation.
#
# --------------------------------- Discovery ----------------------------------
#
# Pass an initial list of hosts to perform discovery when this node is started:
# The default list of hosts is [&quot;127.0.0.1&quot;, &quot;[::1]&quot;]
#
#discovery.seed_hosts: [&quot;host1&quot;, &quot;host2&quot;]
#
# Bootstrap the cluster using an initial set of master-eligible nodes:
#
#cluster.initial_master_nodes: [&quot;node-1&quot;, &quot;node-2&quot;]
#
# For more information, consult the discovery and cluster formation module documentation.
#
# ---------------------------------- Gateway -----------------------------------
#
# Block initial recovery after a full cluster restart until N nodes are started:
#
#gateway.recover_after_nodes: 3
#
# For more information, consult the gateway module documentation.
#
# ---------------------------------- Various -----------------------------------
#
# Require explicit names when deleting indices:
#
#action.destructive_requires_name: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 여러가지 섹션들 중에서 &lt;b&gt;Network&lt;/b&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;- network.host : 엘라스틱서치가 바인딩할 주소를 설정합니다. 기본적으로 루프팩 주소인 127.0.0.1로 바인딩 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- http.port : http 통신을 할 port 번호를 지정합니다. 설정되지 않았다면 9200 포트로 설정됩니다.&lt;/p&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;http.port 옵션은 9200 포트로 사용할 것이기 때문에 별도로 custom하지는 않겠습니다.&lt;span style=&quot;color: #9d9d9d;&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;network.host는 엘라스틱 서치가 루프백 주소가 아닌 GCP의 내부 IP로 호출이 가능하도록 GCP의 내부 IP를 등록해보겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;network.host: 10.178.0.9&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정을 하고 저장한뒤 elasticsearch를 실행해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677915834926&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[2023-03-04T07:41:57,815][INFO ][o.e.b.BootstrapChecks    ] [blackdog-elastic-1] bound or publishing to a non-loopback address, enforcing bootstrap checks
ERROR: [3] bootstrap checks failed
[1]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535]
[2]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
[3]: the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured
ERROR: Elasticsearch did not exit normally - check the logs at /home/wsh0821/elasticsearch-7.11.1/logs/elasticsearch.log
[2023-03-04T07:41:57,869][INFO ][o.e.n.Node               ] [blackdog-elastic-1] stopping ...
[2023-03-04T07:41:57,887][INFO ][o.e.n.Node               ] [blackdog-elastic-1] stopped
[2023-03-04T07:41:57,887][INFO ][o.e.n.Node               ] [blackdog-elastic-1] closing ...
[2023-03-04T07:41:57,905][INFO ][o.e.n.Node               ] [blackdog-elastic-1] closed&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;실행이 잘 되는듯 싶었으나 위와 같은 에러메시지를 만나게 되었습니다. 이제 elasticsearch를 development 모드가 아닌 production 모드로 실행하게 된 것입니다.&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. max file descriptors 4096 is too low, increase to at least 65535 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;-&amp;gt; max file descriptors 라는 설정이 너무 낮으니 올려라.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. max virtual memory areas vm.max_map_count 65530 is too low, increase to at least 262144 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;-&amp;gt; 최대 가상메모리 영역의 vm.max_map_count 설정이 너무 낮으니 올려라.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. the default discovery settings are unsuitable for production use; at least one of[discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;-&amp;gt; 기본 설정이 미흡하니 세가지 옵션중 하나는 설정해놔라&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 Bootstrap Checks 중에서 3가지에 걸려버렸습니다. 이 세가지만 해결하면 일단 production 모드로 실행하여 외부에서도 elasticsearch에 요청할 수 있도록 만들 수 있을 것 같습니다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Max File Descriptors 옵션 값 증가시키기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 옵션을 elasticsearch 공식 레퍼런스에서 서칭하여 아래의 링크를 찾았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/setting-system-settings.html#limits.conf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/setting-system-settings.html#limits.conf&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677916461395&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;Configuring system settings | Elasticsearch Guide [7.11] | Elastic&quot; data-og-description=&quot;Ubuntu and limits.conf Ubuntu ignores the limits.conf file for processes started by init.d. To enable the limits.conf file, edit /etc/pam.d/su and uncomment the following line: # session required pam_limits.so&quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/setting-system-settings.html#limits.conf&quot; data-og-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/setting-system-settings.html#limits.conf&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/setting-system-settings.html#limits.conf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/setting-system-settings.html#limits.conf&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;Configuring system settings | Elasticsearch Guide [7.11] | Elastic&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Ubuntu and limits.conf Ubuntu ignores the limits.conf file for processes started by init.d. To enable the limits.conf file, edit /etc/pam.d/su and uncomment the following line: # session required pam_limits.so&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중 &lt;b&gt;/etc/security/limits.conf&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;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;elasticsearch  -  nofile  65535&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중에서 elasticsearch라는 이름은 서버에 접속한 유저 이름이니 이는 각자 확인해주시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/etc/security/limits.conf 파일은 관리자 파일이니 sudo 명령어로 수정해줍니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;sudo vi /etc/security/limits.conf&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;명령어 입력 후, 파일에 아래 내용 기재&lt;b&gt;&lt;br /&gt;&lt;br /&gt;wsh0821&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;nbsp;nofile&amp;nbsp;&amp;nbsp;65535&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 설정이 마무리됐는지 확인하기위해 elasticsearch를 실행해보려고 하였으나 해당 설정은 서버를 재시작하여야 적용이 된다길래 우선 아래의 내용들을 다 설정해놓고 재시작하도록 하겠습니다.&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;b&gt;vm.max_map_count 옵션 값 증가시키기&lt;/b&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;&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/vm-max-map-count.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/vm-max-map-count.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677916947345&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;Virtual memory | Elasticsearch Guide [7.11] | Elastic&quot; data-og-description=&quot;Elasticsearch uses a mmapfs directory by default to store its indices. The default operating system limits on mmap counts is likely to be too low, which may result in out of memory exceptions. On Linux, you can increase the limits by running the following &quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/vm-max-map-count.html&quot; data-og-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/vm-max-map-count.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/vm-max-map-count.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.11/vm-max-map-count.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;Virtual memory | Elasticsearch Guide [7.11] | Elastic&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Elasticsearch uses a mmapfs directory by default to store its indices. The default operating system limits on mmap counts is likely to be too low, which may result in out of memory exceptions. On Linux, you can increase the limits by running the following&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file descriptors 처럼 서버에 영구적으로 반영하기 위해서는 &lt;b&gt;/etc/sysctl.conf&lt;/b&gt; 파일을 수정해주어야 합니다. 해당 파일을 열어서 아래의 내용을 기재해주도록 하겠습니다. 해당 파일은 역시 관리자 권한이기 때문에 sudo 명령어로 열겠습니다.&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;&lt;b&gt;sudo&amp;nbsp;vi&amp;nbsp;/etc/sysctl.conf&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;명령어 입력 후, 파일에 아래 내용 기재&lt;b&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;vm.max_map_count=262144&lt;br /&gt;&lt;/b&gt;&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;discovery 설정하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 설정을 하기 위해서는 엘라스틱서치의 config 폴더내에 &lt;b&gt;elasticsearch.yml&lt;/b&gt; 파일을 수정해야합니다. 위에서 이미 network.host 옵션을 통해서 ip 설정을 해놓았을텐데요. 여기에 추가적으로 아래의 설정들을 추가해주어야 합니다.&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;&lt;b&gt;cluster.name:&amp;nbsp;&quot;blackdog-elastic&quot;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;node.name:&amp;nbsp;&quot;blackdog-elastic-node-1&quot;&lt;/b&gt;&lt;br /&gt;network.host:&amp;nbsp;[&quot;10.178.0.9&quot;]&lt;br /&gt;&lt;b&gt;discovery.seed_hosts:&amp;nbsp;[&quot;blackdog-elastic-1&quot;]&lt;/b&gt;&lt;br /&gt;&lt;b&gt;cluster.initial_master_nodes:&amp;nbsp;[&quot;blackdog-elastic-node-1&quot;]&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;network.host 옵션을 제외한 네가지 옵션을 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;cluster.name&lt;/b&gt; : cluster란 엘라스틱서치의 실행 그룹입니다. 어떤 실행 그룹에 종속될지 기재합니다. 실행중인 cluster가 없으면 스스로 cluster를 구성하여 single node로 구동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;node.name&lt;/b&gt; : node란 엘라스틱 서치 어플리케이션 하나를 의미합니다. node 단위로 실행되긴하지만 운영단위는 cluster이기 때문에 node의 이름을 기재하여 cluster가 자신의 하위 노드들을 알 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;discovery.seed_hosts&lt;/b&gt; : 엘라스틱서치가 실행할 때 Discovery라는 과정을 거쳐 실행하는데, 이는 클러스터가 될 노드들을 찾아다니는 행위입니다. 여기에 클러스터링할 노드를 최소한 기재해주어야 production 모드로 실행할 수 있습니다. 저는 현재 single node로 실행할 것이기 때문에 현재 엘라스틱 서치가 실행중인 서버의 hostname을 적어주겠습니다.&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(hostname을 알 수 있는 방법은 명령어로 hostname을 치면 됩니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;cluster.initial_master_nodes&lt;/b&gt; : 엘라스틱 서치는 n개의 노드를 클러스터링을 할 때, 마스터 노드와 데이터 노드로 구분됩니다. 이때 여기에 기재되는 노드들이 마스터 노드 후보가되어 노드 클러스터링을 관리합니다. 따라서 single node cluster로 실행할 예정이기 때문에 node.name 옵션에 설정된 노드 이름을 적어주도록 하겠습니다.&lt;/p&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;sudo&amp;nbsp;shutdown&amp;nbsp;-r&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 엘라스틱서치를 시작하고 제 맥북 터미널로 해당 서버의 9200번포트에 curl로 요청을 하나 넣어보겠습니다.&lt;/p&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkpEKI/btr1KwEH71I/WPoKnhzK164YLpxU4TlVT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkpEKI/btr1KwEH71I/WPoKnhzK164YLpxU4TlVT1/img.png&quot; data-alt=&quot;외부에서 elasticsearch 정보 요청 성공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkpEKI/btr1KwEH71I/WPoKnhzK164YLpxU4TlVT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkpEKI%2Fbtr1KwEH71I%2FWPoKnhzK164YLpxU4TlVT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;321&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;외부에서 elasticsearch 정보 요청 성공&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;성공적으로 elasticsearch가 설치된 외부에서도 요청을 받아들이는 것을 확인할 수 있습니다.&lt;/p&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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://www.youtube.com/@elastic7014&quot;&gt;https://www.youtube.com/@elastic7014&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677912591465&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;profile&quot; data-og-title=&quot;한국 Elastic 사용자 그룹&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.youtube.com&quot; data-og-source-url=&quot;https://www.youtube.com/@elastic7014&quot; data-og-url=&quot;https://www.youtube.com/channel/UCmI0g3ROh1WrUMZDv2kcjfQ&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/EUB55/hyRQnt7kNV/DK8zr9RgEwawbqg02Kb1K1/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900,https://scrap.kakaocdn.net/dn/ba1ut1/hyROTg7qHV/vFSrBGrohcpMViagacwvF0/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900&quot;&gt;&lt;a href=&quot;https://www.youtube.com/@elastic7014&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.youtube.com/@elastic7014&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/EUB55/hyRQnt7kNV/DK8zr9RgEwawbqg02Kb1K1/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900,https://scrap.kakaocdn.net/dn/ba1ut1/hyROTg7qHV/vFSrBGrohcpMViagacwvF0/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900');&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;한국 Elastic 사용자 그룹&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.youtube.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>ELK/elasticsearch</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/493</guid>
      <comments>https://sas-study.tistory.com/493#entry493comment</comments>
      <pubDate>Sat, 4 Mar 2023 17:34:50 +0900</pubDate>
    </item>
    <item>
      <title>[Elasticsearch] 엘라스틱 서치 설치하기</title>
      <link>https://sas-study.tistory.com/492</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;이전 포스팅이 대부분 Google Cloud VM 인스턴스에 관한 내용이었을텐데요. 포스팅도 포스팅이지만 사실은 엘라스틱서치를 학습하기 위해서 &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(로컬에 해도됩니다만 지저분해지는걸 싫어해서 클라우드에 하는편입니다.) &lt;span style=&quot;color: #000000;&quot;&gt;클라우드 환경이 필요했는데요.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/489&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/489&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677907460913&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;[GCP] VM 인스턴스 생성하기&quot; data-og-description=&quot;안녕하세요. 최근에 Elastic Search에 대한 스터디를 하는 도중 cloud 서비스 중 GCP를 사용할 일이 생겼고, GCP 관련 서비스에 대한 포스팅은 없던 것 같아서 학습 기록차 공유하고자 합니다. 저는 우선&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/489&quot; data-og-url=&quot;https://sas-study.tistory.com/489&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pEZEm/hyRQlXlBA1/kDehIzfDd8yyU0kkGDZbX1/img.png?width=800&amp;amp;height=1420&amp;amp;face=0_0_800_1420,https://scrap.kakaocdn.net/dn/hVbH1/hyROWZe3Uh/1f7zWWbB32Utyh13n3PqwK/img.png?width=800&amp;amp;height=1420&amp;amp;face=0_0_800_1420,https://scrap.kakaocdn.net/dn/pdtmv/hyROLQS1QZ/9WhtC6kSUreRRMKZj0kNiK/img.png?width=1086&amp;amp;height=654&amp;amp;face=0_0_1086_654&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/489&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/489&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pEZEm/hyRQlXlBA1/kDehIzfDd8yyU0kkGDZbX1/img.png?width=800&amp;amp;height=1420&amp;amp;face=0_0_800_1420,https://scrap.kakaocdn.net/dn/hVbH1/hyROWZe3Uh/1f7zWWbB32Utyh13n3PqwK/img.png?width=800&amp;amp;height=1420&amp;amp;face=0_0_800_1420,https://scrap.kakaocdn.net/dn/pdtmv/hyROLQS1QZ/9WhtC6kSUreRRMKZj0kNiK/img.png?width=1086&amp;amp;height=654&amp;amp;face=0_0_1086_654');&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;[GCP] VM 인스턴스 생성하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 최근에 Elastic Search에 대한 스터디를 하는 도중 cloud 서비스 중 GCP를 사용할 일이 생겼고, GCP 관련 서비스에 대한 포스팅은 없던 것 같아서 학습 기록차 공유하고자 합니다. 저는 우선&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sas-study.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;a href=&quot;https://sas-study.tistory.com/490&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/490&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677907403196&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;[GCP] VM 인스턴스를 터미널 도구를 이용하여 SSH 접속 설정&quot; data-og-description=&quot;안녕하세요. 지난 gcp vm 인스턴스 생성 예제에 이어서 해당 vm에 접속하는 방법에 대해서 공유하고자 합니다. 저는 우선 두가지 방법을 주로 사용하고 있는데요. 1. gcp 내부에서 제공하는 ssh를 사&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/490&quot; data-og-url=&quot;https://sas-study.tistory.com/490&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JQPHb/hyROIzSVeN/WUpKh3Cwt59no5UQZu5p1k/img.png?width=800&amp;amp;height=256&amp;amp;face=0_0_800_256,https://scrap.kakaocdn.net/dn/OxAj4/hyROUUBFAO/jbMxY9PAN1V9rGWVqWItAK/img.png?width=800&amp;amp;height=256&amp;amp;face=0_0_800_256,https://scrap.kakaocdn.net/dn/rfjy6/hyROLciQze/5yqWvYoPB8ygK31qjhBPFk/img.png?width=1844&amp;amp;height=1438&amp;amp;face=0_0_1844_1438&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/490&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/490&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JQPHb/hyROIzSVeN/WUpKh3Cwt59no5UQZu5p1k/img.png?width=800&amp;amp;height=256&amp;amp;face=0_0_800_256,https://scrap.kakaocdn.net/dn/OxAj4/hyROUUBFAO/jbMxY9PAN1V9rGWVqWItAK/img.png?width=800&amp;amp;height=256&amp;amp;face=0_0_800_256,https://scrap.kakaocdn.net/dn/rfjy6/hyROLciQze/5yqWvYoPB8ygK31qjhBPFk/img.png?width=1844&amp;amp;height=1438&amp;amp;face=0_0_1844_1438');&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;[GCP] VM 인스턴스를 터미널 도구를 이용하여 SSH 접속 설정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 지난 gcp vm 인스턴스 생성 예제에 이어서 해당 vm에 접속하는 방법에 대해서 공유하고자 합니다. 저는 우선 두가지 방법을 주로 사용하고 있는데요. 1. gcp 내부에서 제공하는 ssh를 사&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sas-study.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;a href=&quot;https://sas-study.tistory.com/491&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/491&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677907465333&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;[GCP] VM 인스턴스 방화벽 설정(port 설정)&quot; data-og-description=&quot;안녕하세요. 지난 포스팅에서는 - [GCP] VM 인스턴스 생성하기 - [GCP] VM 인스턴스를 터미널 도구를 이용하여 SSH 접속 설정 두가지 내용을 설정해봤는데요. 저같은 경우는 이제 ssh로 접속해서 elastics&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/491&quot; data-og-url=&quot;https://sas-study.tistory.com/491&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/w8L4M/hyROWx8uMe/FjFuamwNMvFuW46do8Eqtk/img.png?width=800&amp;amp;height=1145&amp;amp;face=0_0_800_1145,https://scrap.kakaocdn.net/dn/SbMnB/hyRQjFed7n/WzClvB7FTXgBlSIE705luk/img.png?width=800&amp;amp;height=1145&amp;amp;face=0_0_800_1145,https://scrap.kakaocdn.net/dn/bi8Viw/hyRQlbYGQa/0FWcMj6wbQmXiIDR3SsE5k/img.png?width=830&amp;amp;height=1188&amp;amp;face=0_0_830_1188&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/491&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/491&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/w8L4M/hyROWx8uMe/FjFuamwNMvFuW46do8Eqtk/img.png?width=800&amp;amp;height=1145&amp;amp;face=0_0_800_1145,https://scrap.kakaocdn.net/dn/SbMnB/hyRQjFed7n/WzClvB7FTXgBlSIE705luk/img.png?width=800&amp;amp;height=1145&amp;amp;face=0_0_800_1145,https://scrap.kakaocdn.net/dn/bi8Viw/hyRQlbYGQa/0FWcMj6wbQmXiIDR3SsE5k/img.png?width=830&amp;amp;height=1188&amp;amp;face=0_0_830_1188');&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;[GCP] VM 인스턴스 방화벽 설정(port 설정)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 지난 포스팅에서는 - [GCP] VM 인스턴스 생성하기 - [GCP] VM 인스턴스를 터미널 도구를 이용하여 SSH 접속 설정 두가지 내용을 설정해봤는데요. 저같은 경우는 이제 ssh로 접속해서 elastics&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sas-study.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;위의 링크는 엘라스틱 서치를 하는 도중에 마주친 문제들 혹은 방법들을 정리한 포스팅이니 엘라스틱 서치를 설치해보시기 전에 해당 포스팅을 통해 클라우드 서버 하나를 확보하시는 편이 좋을 것 같습니다. 이는 위처럼 GCP를 선택하셔도 좋고 이미 클라우드 서비스 사용하시는 업체가 있으시다면 해당 업체의 서비스 하나로 메모리 4G 정도되는 서버 사이즈를 선택해주시면 좋을 것 같습니다.&lt;/p&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는 centOS입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 하나가 확보되었다면 이제 엘라스틱서치를 설치해야하는데요. 엘라스틱서치를 설치하는 방법은 여러가지가 있겠지만 저는 wget을 활용해서 직접 다운로드 사이트에서 압축파일을 다운로드 받아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해 우선, wget이 설치가 되어있는지 확인이 필요할텐데요. gcp에는 기본적으로 설치가 되어있지는 않는것 같아서 wget을 설치하겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;sudo&amp;nbsp;yum&amp;nbsp;install&amp;nbsp;wget&lt;/b&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;그 전에 우선 본인이 선택하신 OS를 확인을 꼭 하시구요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/support/matrix#matrix_os&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.elastic.co/kr/support/matrix#matrix_os&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677907971374&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;*Endgame 센서 3.51-3.54의 경우, 32비트 및 64비트 아키텍처가 지원되고, 3.54 이후의 버전에서는 64비트 아키텍처만 지원됩니다(아래에서 언급된 대로 Windows 7 SP1은 제외). **모든 센서 버전 Windows 7 SP1의&quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/kr/support/matrix#matrix_os&quot; data-og-url=&quot;https://www.elastic.co/kr/support/matrix#matrix_os&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nI58q/hyROJyOD3h/MgfoHuyobN7OZqp86euYf0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/dxJGra/hyROU1mjot/9HXJmzAsTotL9QlCAXxSt0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/support/matrix#matrix_os&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/kr/support/matrix#matrix_os&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nI58q/hyROJyOD3h/MgfoHuyobN7OZqp86euYf0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/dxJGra/hyROU1mjot/9HXJmzAsTotL9QlCAXxSt0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837');&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;*Endgame 센서 3.51-3.54의 경우, 32비트 및 64비트 아키텍처가 지원되고, 3.54 이후의 버전에서는 64비트 아키텍처만 지원됩니다(아래에서 언급된 대로 Windows 7 SP1은 제외). **모든 센서 버전 Windows 7 SP1의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;아래의 링크로 접속해서 엘라스틱서치를 찾고 버전을 선택해주세요. 저는 7.11.1 버전을 선택하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/downloads/past-releases#elasticsearch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.elastic.co/kr/downloads/past-releases#elasticsearch&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677908133971&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;Past Releases of Elastic Stack Software&quot; data-og-description=&quot;Looking for a past release of Elasticsearch, Logstash, Kibana, es-hadoop, Shield, Marvel, or our language clients? You're in the right place.&quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/kr/downloads/past-releases#elasticsearch&quot; data-og-url=&quot;https://www.elastic.co/kr/downloads/past-releases#elasticsearch&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bggavh/hyRQs3fh0J/G5rpzKjUquuwiJoCRkrKU0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/xI5me/hyRQqYE8Vk/OphgateoGJ7tbFECqdfCs1/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837&quot;&gt;&lt;a href=&quot;https://www.elastic.co/kr/downloads/past-releases#elasticsearch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/kr/downloads/past-releases#elasticsearch&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bggavh/hyRQs3fh0J/G5rpzKjUquuwiJoCRkrKU0/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837,https://scrap.kakaocdn.net/dn/xI5me/hyRQqYE8Vk/OphgateoGJ7tbFECqdfCs1/img.png?width=1600&amp;amp;height=837&amp;amp;face=0_0_1600_837');&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;Past Releases of Elastic Stack Software&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Looking for a past release of Elasticsearch, Logstash, Kibana, es-hadoop, Shield, Marvel, or our language clients? You're in the right place.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2534&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJd0eN/btr1T5e37pX/u0Iaat00Rqp2C7HvO4nRw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJd0eN/btr1T5e37pX/u0Iaat00Rqp2C7HvO4nRw1/img.png&quot; data-alt=&quot;elasticsearch 7.11.1 버전 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJd0eN/btr1T5e37pX/u0Iaat00Rqp2C7HvO4nRw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJd0eN%2Fbtr1T5e37pX%2Fu0Iaat00Rqp2C7HvO4nRw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;548&quot; data-origin-width=&quot;2534&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;elasticsearch 7.11.1 버전 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 사용할 OS는 CentOS이며 64비트이니 아래처럼 LINUX_X86_64 를 우클릭하고 링크를 복사해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;1012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZNHm6/btr1Mj51tJc/Tdcc9ciJS0sps8RXQ1v0dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZNHm6/btr1Mj51tJc/Tdcc9ciJS0sps8RXQ1v0dK/img.png&quot; data-alt=&quot;OS에 맞는 파일 링크 주소 복사&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZNHm6/btr1Mj51tJc/Tdcc9ciJS0sps8RXQ1v0dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZNHm6%2Fbtr1Mj51tJc%2FTdcc9ciJS0sps8RXQ1v0dK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1012&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;1012&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OS에 맞는 파일 링크 주소 복사&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다시 서버 클라이언트로 돌아와서 wget을 이용해서 해당 주소의 파일을 다운로드합니다.&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;&lt;b&gt;wget &lt;/b&gt;&lt;a href=&quot;https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.11.1-linux-x86_64.tar.gz&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.11.1-linux-x86_64.tar.gz&lt;/b&gt;&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sIPtS/btr1TTzmwqL/HKVkyw9sOhm1KKkNy0DfBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sIPtS/btr1TTzmwqL/HKVkyw9sOhm1KKkNy0DfBK/img.png&quot; data-alt=&quot;elasticsearch 설치파일이 다운로드된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sIPtS/btr1TTzmwqL/HKVkyw9sOhm1KKkNy0DfBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsIPtS%2Fbtr1TTzmwqL%2FHKVkyw9sOhm1KKkNy0DfBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;656&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;elasticsearch 설치파일이 다운로드된 모습&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;이제 다운로드 되었으니 압축파일을 해제하겠습니다. 압축을 해제하는 명령어는 tar 명령어를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;tar xfz elasticsearch-7.11.1-linux-x86_64.tar.gz&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2OY1L/btr16jXdOUm/kwIxK1NfkvBXTPiKo3vUkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2OY1L/btr16jXdOUm/kwIxK1NfkvBXTPiKo3vUkK/img.png&quot; data-alt=&quot;압축해제된게 확인된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2OY1L/btr16jXdOUm/kwIxK1NfkvBXTPiKo3vUkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2OY1L%2Fbtr16jXdOUm%2FkwIxK1NfkvBXTPiKo3vUkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;60&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;136&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;실행해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;elasticsearch-7.11.1 디렉토리로 들어가서 bin 디렉토리를 찾아 한번 더 들어갑니다. 요 bin 디렉토리 안에 있는 elasticsearch 파일을 실행해주면 끝입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;./elasticsearch&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r8k0q/btr1Y7XdT7Q/cTwV3Cv9qkciyHs3aSYSj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r8k0q/btr1Y7XdT7Q/cTwV3Cv9qkciyHs3aSYSj1/img.png&quot; data-alt=&quot;엘라스틱서치 실행 명령어&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r8k0q/btr1Y7XdT7Q/cTwV3Cv9qkciyHs3aSYSj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr8k0q%2Fbtr1Y7XdT7Q%2FcTwV3Cv9qkciyHs3aSYSj1%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;1136&quot; height=&quot;136&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;136&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;기본적으로 엘라스틱서치는 9200 포트에 연결됩니다.(설정을 통해 바꿀 수 있습니다.) 하지만 현재는 클라우드 보안설정에서 9200 포트를 오픈했다고 하더라도 외부에서 해당 ip의 9200 포트로 요청을 해도 elasticsearch는 응답을 주지 못합니다. 왜냐하면 별도의 설정이 추가되지 않으면 엘라스틱서치는 해당 세션을 개발용 실행으로 실행합니다. 운영용으로 실행해주기 위해서는 &lt;b&gt;bootstrap check&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;따라서 ssh 클라이언트를 사용해서 다른 세션을 열고 새로 하나 더 연결해보겠습니다. 그리고&amp;nbsp; curl 명령어로 외부 ip 주소가 아닌 localhost 로 접근해주겠습니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcxEbi/btr1QyIB0e4/TJ4535STYtAKyIt69a2S30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcxEbi/btr1QyIB0e4/TJ4535STYtAKyIt69a2S30/img.png&quot; data-alt=&quot;localhost로 접근&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcxEbi/btr1QyIB0e4/TJ4535STYtAKyIt69a2S30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcxEbi%2Fbtr1QyIB0e4%2FTJ4535STYtAKyIt69a2S30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;604&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;localhost로 접근&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;https://www.youtube.com/@elastic7014&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/@elastic7014&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677910225454&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;profile&quot; data-og-title=&quot;한국 Elastic 사용자 그룹&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.youtube.com&quot; data-og-source-url=&quot;https://www.youtube.com/@elastic7014&quot; data-og-url=&quot;https://www.youtube.com/channel/UCmI0g3ROh1WrUMZDv2kcjfQ&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/EUB55/hyRQnt7kNV/DK8zr9RgEwawbqg02Kb1K1/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900,https://scrap.kakaocdn.net/dn/ba1ut1/hyROTg7qHV/vFSrBGrohcpMViagacwvF0/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900&quot;&gt;&lt;a href=&quot;https://www.youtube.com/@elastic7014&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.youtube.com/@elastic7014&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/EUB55/hyRQnt7kNV/DK8zr9RgEwawbqg02Kb1K1/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900,https://scrap.kakaocdn.net/dn/ba1ut1/hyROTg7qHV/vFSrBGrohcpMViagacwvF0/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900');&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;한국 Elastic 사용자 그룹&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.youtube.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>ELK/elasticsearch</category>
      <category>elasticsearch install</category>
      <category>install elasticsearch</category>
      <category>엘라스틱서치 설치</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/492</guid>
      <comments>https://sas-study.tistory.com/492#entry492comment</comments>
      <pubDate>Sat, 4 Mar 2023 14:55:44 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] VM 인스턴스 방화벽 설정(port 설정)</title>
      <link>https://sas-study.tistory.com/491</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;- &lt;a href=&quot;https://sas-study.tistory.com/489&quot;&gt;[GCP] VM 인스턴스 생성하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://sas-study.tistory.com/490&quot;&gt;[GCP] VM 인스턴스를 터미널 도구를 이용하여 SSH 접속 설정&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;두가지 내용을 설정해봤는데요. 저같은 경우는 이제 ssh로 접속해서 elasticsearch를 설치한 후 잘 설치가 되었는지 외부에서 elasticsearch에 간단한 health check 요청을 해봐야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헌데 aws를 사용해보신분들은 아시겠지만 aws 에서는 ec2 에 연결된 보안그룹에서 사용하고자 하는 포트번호가 외부로 open이 되어있어야 서버가 요청을 받아들일 수 있었을텐데요. gcp에서도 비슷한 작업을 해줘야합니다.&lt;/p&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;1188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2NJdZ/btr1KO4LELd/8l3wA0wL9KSk98o9DQ4pKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2NJdZ/btr1KO4LELd/8l3wA0wL9KSk98o9DQ4pKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2NJdZ/btr1KO4LELd/8l3wA0wL9KSk98o9DQ4pKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2NJdZ%2Fbtr1KO4LELd%2F8l3wA0wL9KSk98o9DQ4pKk%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;400&quot; height=&quot;1188&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;1188&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희가 만들어야될 것은 &lt;b&gt;방화벽 규칙&lt;/b&gt;입니다. 이전에 vm 인스턴스를 만들때 http와 https 트래픽요청을 모두 허용으로 해놨기 때문에 gcp에서 자동으로 생성해놓은 default 방화벽 규칙들이 보일것입니다. 하지만 이는 ssh 연결을 위한 22, http 80, https 443 과 같은 약속된 프로토콜 포트입니다. 저는 elasticsearch를 열어놓은 9200 포트를 열어야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 저는 9200 포트를 오픈하는 방화벽 규칙을 생성할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdsKAM/btr1Mn6UPBu/EdKHDUKfxFakIVweLiWGLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdsKAM/btr1Mn6UPBu/EdKHDUKfxFakIVweLiWGLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdsKAM/btr1Mn6UPBu/EdKHDUKfxFakIVweLiWGLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdsKAM%2Fbtr1Mn6UPBu%2FEdKHDUKfxFakIVweLiWGLk%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;700&quot; height=&quot;582&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;우선 위의 그림처럼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&quot;방화벽 규칙 만들기&quot;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;라는 버튼을 클릭합니다. 방화벽 정책 만들기와 혼동되시면 안됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1060&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baYsts/btr1p2Rd6vs/pTDjivHEbBqQDIFd4Vl7lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baYsts/btr1p2Rd6vs/pTDjivHEbBqQDIFd4Vl7lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baYsts/btr1p2Rd6vs/pTDjivHEbBqQDIFd4Vl7lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaYsts%2Fbtr1p2Rd6vs%2FpTDjivHEbBqQDIFd4Vl7lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;472&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1060&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 우선 이름과 설명을 9200 번 포트를 오픈하기 위한 방화벽 규칙임을 명시하였습니다. 로그 쪽 내용은 변경하지 않고 디폴트 옵션을 적용하였습니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9EQPx/btr1MjwD2FZ/BOkqLjuvjpyuiTF1HVcOm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9EQPx/btr1MjwD2FZ/BOkqLjuvjpyuiTF1HVcOm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9EQPx/btr1MjwD2FZ/BOkqLjuvjpyuiTF1HVcOm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9EQPx%2Fbtr1MjwD2FZ%2FBOkqLjuvjpyuiTF1HVcOm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;748&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방화벽 대상을 지정하는 방식은 태그를 지정하는 방식으로 선택하였고 해당 태그를 &quot;port9200&quot;으로 지었습니다. 뒤에서 vm 인스턴스에 해당 태그를 지정하여 설정할 수 있습니다. 소스필터는 IPv4 범위내 모든 위치에서의 요청을 허용하였습니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMPzVD/btr1pXiipnj/bPks3h8GWJSGWlcPEHgTZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMPzVD/btr1pXiipnj/bPks3h8GWJSGWlcPEHgTZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMPzVD/btr1pXiipnj/bPks3h8GWJSGWlcPEHgTZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMPzVD%2Fbtr1pXiipnj%2FbPks3h8GWJSGWlcPEHgTZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;424&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;926&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜 및 포트는 tcp port 9200 번을 지정해줍니다. 다른 포트는 필요없으니 9200 만 명시하고 만들기 버튼을 클릭합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 방화벽 대쉬보드 화면에서 제가 만든 port9200 방화벽 규칙을 확인할 수 있게됐습니다. 이것을 vm 인스턴스에 적용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방화벽 규칙은 vm 인스턴스 수정창에서 진행할 수 있습니다. vm 인스턴스 목록에서 대상 vm 인스턴스를 선택하여 상세로 오면 &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;b&gt;네트워크 인터페이스&lt;/b&gt;라는 소제목에서 멈춥니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qBcRw/btr1MnFP60S/HHFqsK1cBUdtJL4CuFjdo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qBcRw/btr1MnFP60S/HHFqsK1cBUdtJL4CuFjdo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qBcRw/btr1MnFP60S/HHFqsK1cBUdtJL4CuFjdo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqBcRw%2Fbtr1MnFP60S%2FHHFqsK1cBUdtJL4CuFjdo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;712&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 것들은 다 내비두고 네트워크 태그에서 제가 만든 방화벽 규칙인 &lt;b&gt;port9200&lt;/b&gt;을 태그로 지정해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데!! 적용하기전에 vm 인스턴스 서버의 9200 포트에 요청을 날려보았더니 block 된것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;1008&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1w2Ko/btr1HiFzfi6/xU463xdkYISTk5nmBh79FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1w2Ko/btr1HiFzfi6/xU463xdkYISTk5nmBh79FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1w2Ko/btr1HiFzfi6/xU463xdkYISTk5nmBh79FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1w2Ko%2Fbtr1HiFzfi6%2FxU463xdkYISTk5nmBh79FK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1008&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;1008&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인이 끝났으면 저장 버튼을 눌러줍니다. 다시한번 브라우저를 새로고침하여 재요청을하니 9200 포트를 통해서 elasticsearch의 데이터 정보를 받아올 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;1012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DF5zI/btr1yA06PmD/n9KlkGkyJfMaP2Kyl8tK8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DF5zI/btr1yA06PmD/n9KlkGkyJfMaP2Kyl8tK8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DF5zI/btr1yA06PmD/n9KlkGkyJfMaP2Kyl8tK8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDF5zI%2Fbtr1yA06PmD%2Fn9KlkGkyJfMaP2Kyl8tK8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1012&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;1012&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;이로인해 vm 인스턴스의 9200 포트를 통해 elasticsearch와 브라우저간의 데이터 통신을 확인할 수 있었습니다.&lt;/p&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>GCP</category>
      <category>gcp http port 설정</category>
      <category>gcp port open</category>
      <category>gcp port 설정</category>
      <category>gcp 방화벽 규칙</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/491</guid>
      <comments>https://sas-study.tistory.com/491#entry491comment</comments>
      <pubDate>Fri, 3 Mar 2023 01:45:03 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] VM 인스턴스를 터미널 도구를 이용하여 SSH 접속 설정</title>
      <link>https://sas-study.tistory.com/490</link>
      <description>&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://sas-study.tistory.com/489&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;gcp vm 인스턴스 생성&lt;/a&gt; 예제에 이어서 해당 vm에 접속하는 방법에 대해서 공유하고자 합니다.&lt;/p&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;1. gcp 내부에서 제공하는 ssh를 사용하여 브라우저창에서 접속&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. mac 의 terminal 혹은 iterms와 같은 client 툴을 활용하여 접속.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;b&gt;브라우저창에서 SSH 접속&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws와 달리 gcp에서는 브라우저에서 물리서버에 접근할 수 있도록 ssh설정을 미리 해줍니다. 반면 aws는 pem 키 혹은 ppk 설정을 통해서 개발자한테 맡기는 형태로 알고 있는데 이점은 gcp의 간편한 방법이라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vm 인스턴스 목록에서 연결탭에 SSH라는 글자를 찾아보실 수 있을겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2788&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLjJgB/btr1CMUsitP/WNIWs6gCUSUII5tKeUf15K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLjJgB/btr1CMUsitP/WNIWs6gCUSUII5tKeUf15K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLjJgB/btr1CMUsitP/WNIWs6gCUSUII5tKeUf15K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLjJgB%2Fbtr1CMUsitP%2FWNIWs6gCUSUII5tKeUf15K%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;2788&quot; height=&quot;894&quot; data-origin-width=&quot;2788&quot; data-origin-height=&quot;894&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&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cV8g5M/btr1zmVMV6m/26N11zNEbkWuGtnXmxymA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cV8g5M/btr1zmVMV6m/26N11zNEbkWuGtnXmxymA1/img.png&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;1438&quot; data-is-animation=&quot;false&quot; width=&quot;400&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cV8g5M/btr1zmVMV6m/26N11zNEbkWuGtnXmxymA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcV8g5M%2Fbtr1zmVMV6m%2F26N11zNEbkWuGtnXmxymA1%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;1844&quot; height=&quot;1438&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJMjU5/btr1CM70O1g/23raAIgIgbX4h707VPfIuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJMjU5/btr1CM70O1g/23raAIgIgbX4h707VPfIuK/img.png&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;1438&quot; data-is-animation=&quot;false&quot; width=&quot;400&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJMjU5/btr1CM70O1g/23raAIgIgbX4h707VPfIuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJMjU5%2Fbtr1CM70O1g%2F23raAIgIgbX4h707VPfIuK%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;1844&quot; height=&quot;1438&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 왼쪽 화면처럼 &quot;ssh키를 vm에 전송중입니다.&quot; 라는 문장과 함께 내부적으로 ssh 접속을 시도합니다. 그리고 이내 몇초가 되지 않아 오른쪽 화면처럼 ssh연결이 완료됩니다. gcp에서는 이렇게 vm에 직접 브라우저로 접속할 수 있도록 강력한 기능을 제공합니다. 클라이언트 입장에서는 서버를 이용하는데 있어서 별도의 귀찮은 공개키 작업이 들어가지 않아서 좋긴 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 큰 단점이 있습니다. 바로 브라우저가 가진 단점이기도 한데요.. 바로 브라우저 세션이 만료되면 해당 세션도 만료되어 새로고침을 하더라도 재연결하지 못하고 다시 gcp vm 목록에서 위에서 하던 행동을 똑같이 해주어야 한다는 것입니다. 이것은 조금 안타깝습니다ㅠㅠ 그리고 본인은 mac 사용자인지라 그많은 크롬중에서 저 조그만걸 찾기란 어렵기도 하고요..ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;외부 Client로 접속하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 저는 외부 Client로 접속하여 사용합니다. 공개키 이런 작업이 귀찮기는하지만 한번 귀찮을 작업을 해두면 브라우저 세션에 구애받지 않고 작업을 쉽게할 수 있는 것 같습니다.&lt;/p&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-origin-width=&quot;2788&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nGNSb/btr1zmaqw1Y/X5R9191RhKUlJRlMZ81ZN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nGNSb/btr1zmaqw1Y/X5R9191RhKUlJRlMZ81ZN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nGNSb/btr1zmaqw1Y/X5R9191RhKUlJRlMZ81ZN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnGNSb%2Fbtr1zmaqw1Y%2FX5R9191RhKUlJRlMZ81ZN0%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;2788&quot; height=&quot;890&quot; data-origin-width=&quot;2788&quot; data-origin-height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 &lt;b&gt;&quot;다른 SSH 클라이언트 사용&quot;&lt;/b&gt; 버튼을 눌러보면 &lt;a href=&quot;https://cloud.google.com/compute/docs/instances/connecting-advanced?hl=ko#thirdpartytools&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;해당 링크&lt;/a&gt;로 연결되는데.. (혹시 시대가 지나 해당 링크가 제대로 안나온다면 댓글로 알려주세요. 업데이트 해놓겠습니다.)&amp;nbsp;&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;해당 페이지를 network에 대해서 빠삭하신분이라면 쉽게쉽게 이해하시겠지만 많은 초급자들은 아닐겁니다. 따라서 그냥 이것만 외워두시면 됩니다.&lt;/p&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 연결에서는 어떤 열쇠가 필요합니다. 그 열쇠는 &lt;b&gt;공개키/개인키(비공개키)&lt;/b&gt; 입니다. 그중 공개키는 gcp 서비스에 등록할 예정이며 우리는 ssh 연결을 할때 개인키를 이용해서 문을 따고 들어갈겁니다. 이는 비대칭키라고 불리우는 암호화 방식이며 이에 대해서는 해당 예제로만 설명하겠습니다. 현재부터는 &lt;u&gt;&lt;b&gt;ssh 키&lt;/b&gt;&lt;/u&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;어쨌든 우리는 ssh 키를 만들어야 gcp vm에 자유롭게 드나들 수 있습니다. 해당 문서에서 다음을 찾아봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;1118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V6fEt/btr1MiRZNAF/SCCj4jjxouesK8IC8Ugdk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V6fEt/btr1MiRZNAF/SCCj4jjxouesK8IC8Ugdk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V6fEt/btr1MiRZNAF/SCCj4jjxouesK8IC8Ugdk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV6fEt%2Fbtr1MiRZNAF%2FSCCj4jjxouesK8IC8Ugdk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;1118&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;1118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cloud.google.com/compute/docs/connect/create-ssh-keys?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;해당 링크&lt;/a&gt;를 클릭하면 접속하실 수 있습니다. 어쨌든 접속했다면 ssh 키를 만들어봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;1266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LieY7/btr1lwd9jTP/z8LuEGO4UhD8yj2oPCckKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LieY7/btr1lwd9jTP/z8LuEGO4UhD8yj2oPCckKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LieY7/btr1lwd9jTP/z8LuEGO4UhD8yj2oPCckKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLieY7%2Fbtr1lwd9jTP%2Fz8LuEGO4UhD8yj2oPCckKk%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;1748&quot; height=&quot;1266&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;1266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh키 쌍을 만들기 위해서는 ssh-keygen 명령어를 사용해야합니다. 그 전에 위의 빨간색 부분을 클릭해서 KEY_FILENAME 과&amp;nbsp; USERNAME을 수정해주세요. 저는 blackdog-elasticsearch 라는 이름의 ssh 키를 만들고 VM 사용자 이름은 wsh0821 입니다. 이부분은 잘 보고 진행해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성이 완료되었다면 복사해줍니다. 해당 명령어를 원하시는 클라이언트 툴에서 입력해줍니다. (본인은 MacOS를 사용하였습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LmD0n/btr1EewlYMX/m5K0REpk0wZ5r6yS1zi5G0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LmD0n/btr1EewlYMX/m5K0REpk0wZ5r6yS1zi5G0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LmD0n/btr1EewlYMX/m5K0REpk0wZ5r6yS1zi5G0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLmD0n%2Fbtr1EewlYMX%2Fm5K0REpk0wZ5r6yS1zi5G0%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;1698&quot; height=&quot;688&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;passphrase는 별도로 입력해주지는 않았고 쭉 엔터를 입력하니 키가 생성되었다는 메시지가 나왔습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chiSNx/btr1HgOvnjL/6QR2RKFYzCR9RFWeYGSYDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chiSNx/btr1HgOvnjL/6QR2RKFYzCR9RFWeYGSYDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chiSNx/btr1HgOvnjL/6QR2RKFYzCR9RFWeYGSYDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchiSNx%2Fbtr1HgOvnjL%2F6QR2RKFYzCR9RFWeYGSYDk%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;136&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~/.ssh 경로 아래에 ssh 키쌍이 잘 생성된 것을 확인하였습니다. 두 파일중에서 .pub으로 끝나는 파일을 출력해봅니다. 출력을 위해 cat 명령어를 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjLujX/btr1HgVgfuC/M0B3vHZkmhy6qVdZl5rihK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjLujX/btr1HgVgfuC/M0B3vHZkmhy6qVdZl5rihK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjLujX/btr1HgVgfuC/M0B3vHZkmhy6qVdZl5rihK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjLujX%2Fbtr1HgVgfuC%2FM0B3vHZkmhy6qVdZl5rihK%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;1550&quot; height=&quot;174&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;174&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;이제 제가 위에서 말했듯 ssh 키 쌍이 생성되었으니 gcp는 이제 이 열쇠를 가지고 본인들의 vm에 드나들 수 있는 유저인지 판단하게 됩니다. 그 판단척도로써 gcp는 공개키를 가져가고 저는 개인키를 소유하여 드나들 수 있는 유저인지 판단하도록 구조를 만들겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 gcp 도큐먼트 페이지에서 맨 아래로 가보시면 다음단계라고 나와있는데 아래와 같이 &quot;VM에 SSH 키를 추가하는 방법&quot;이 있을 것입니다. 이를 클릭해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IFxvW/btr1Kv48F6M/nhwDCZmU5nZiMmaFTNVRzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IFxvW/btr1Kv48F6M/nhwDCZmU5nZiMmaFTNVRzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IFxvW/btr1Kv48F6M/nhwDCZmU5nZiMmaFTNVRzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIFxvW%2Fbtr1Kv48F6M%2FnhwDCZmU5nZiMmaFTNVRzk%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;400&quot; height=&quot;284&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 페이지에는 여러가지 방법이 있는데요. 그 중에서 저는 &lt;b&gt;&quot;프로젝트 메타데이터에 ssh 키 추가&quot; &lt;/b&gt;를 선택해서 진행해보겠습니다. 어차피 해당 vm 인스턴스가 한 프로젝트 내에서 관리되기 때문에 메타데이터에서 공동으로 관리해주는게 편할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkOhZX/btr1CLVxVuY/BamTk0HsOK1or8zErOb121/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkOhZX/btr1CLVxVuY/BamTk0HsOK1or8zErOb121/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkOhZX/btr1CLVxVuY/BamTk0HsOK1or8zErOb121/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkOhZX%2Fbtr1CLVxVuY%2FBamTk0HsOK1or8zErOb121%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;269&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 위치로 가서 &quot;메타데이터 이동&quot; 이라는 버튼을 클릭해줍니다. 그러면 메타데이터 페이지로 이동하게 되는데요. 그중에서 헷갈리지마시고 &lt;b&gt;ssh 키&lt;/b&gt; 라는 탭으로 이동해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2138&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oEelz/btr1EgudHW7/px1zBLZh0GjSUIAkp7nfK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oEelz/btr1EgudHW7/px1zBLZh0GjSUIAkp7nfK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oEelz/btr1EgudHW7/px1zBLZh0GjSUIAkp7nfK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoEelz%2Fbtr1EgudHW7%2Fpx1zBLZh0GjSUIAkp7nfK1%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;2138&quot; height=&quot;618&quot; data-origin-width=&quot;2138&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 기존에 쓰던 ssh 공개키를 등록해놨기 때문에 두개가 이미 있는데 처음 하시는분들은 비어있을 겁니다. 아까 우리가 위에서 cat으로 출력해둔 ssh 키 쌍의 공개키를 여기에 등록만 해주면 됩니다. 수정버튼을 클릭하여 넘어갑니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dsecp/btr1I9OQd9G/X6LZEWIXb3hrN3E2qlPyV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dsecp/btr1I9OQd9G/X6LZEWIXb3hrN3E2qlPyV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dsecp/btr1I9OQd9G/X6LZEWIXb3hrN3E2qlPyV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDsecp%2Fbtr1I9OQd9G%2FX6LZEWIXb3hrN3E2qlPyV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;866&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 cat 명령어로 출력해놓은 공개키를 복사하여 붙여넣기 해주세요. 양 옆에 띄어쓰기 한칸이라도 잘못 복사되면 접속되지 않으니 주의해서 복사해주세요.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 ssh 클라이언트로 돌아와서 ssh 명령어로 접근을 시도합니다.!&lt;/p&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;style3&quot;&gt;&lt;b&gt;ssh -i ~/.ssh/blackdog-elasticsearch wsh0821@34.64.185.153&lt;br /&gt;&lt;br /&gt;ssh -i ~/.ssh/ssh키파일명(.pub 아님주의) vm인스턴스유저명@vm인스턴스IP&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 접속이 되는군요. 고생하셨습니다.&lt;/p&gt;</description>
      <category>GCP</category>
      <category>gcp ssh 접속</category>
      <category>gcp ssh 키 생성</category>
      <category>gcp terminal ssh 접속</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/490</guid>
      <comments>https://sas-study.tistory.com/490#entry490comment</comments>
      <pubDate>Fri, 3 Mar 2023 00:21:42 +0900</pubDate>
    </item>
    <item>
      <title>[GCP] VM 인스턴스 생성하기</title>
      <link>https://sas-study.tistory.com/489</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;최근에 Elastic Search에 대한 스터디를 하는 도중 cloud 서비스 중 GCP를 사용할 일이 생겼고, GCP 관련 서비스에 대한 포스팅은 없던 것 같아서 학습 기록차 공유하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 우선 elasticsearch 학습을 위해 n개의 vm 서버가 필요한 상태였고 해당 소프트웨어를 설치하기 좋은 환경의 vm을 만드는 것을 목표로 하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCP vm 인스턴스 생성을 위해 따라하시는 분들께서는 본인에게 적절한 VM 스펙을 고려하고 따라하시기 바랍니다.(참고로 gcp는 최초가입시 50여 만원의 크레딧을 주고 있는데요. vm의 스펙이 강할수록 빨리 소모됩니다ㅠㅠ 그래서 이런 말씀을 드리는 겁니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. GCP에 가입한다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://console.cloud.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://console.cloud.google.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677765375598&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;Google 클라우드 플랫폼&quot; data-og-description=&quot;로그인 Google 클라우드 플랫폼으로 이동&quot; data-og-host=&quot;accounts.google.com&quot; data-og-source-url=&quot;https://console.cloud.google.com/&quot; data-og-url=&quot;https://accounts.google.com/v3/signin/identifier?dsh=S503806755%3A1677765363763523&amp;amp;continue=https%3A%2F%2Fconsole.cloud.google.com%2F&amp;amp;followup=https%3A%2F%2Fconsole.cloud.google.com%2F&amp;amp;osid=1&amp;amp;passive=1209600&amp;amp;service=cloudconsole&amp;amp;flowName=WebLiteSignIn&amp;amp;flowEntry=ServiceLogin&amp;amp;ifkv=AWnogHfabnrEbdRk5tYzLQ-HCGx8sZC1GuYiuJJ0_kH5XyBQCIPdSjavxhPPNZ5lrvch2XK-odGTmw&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://console.cloud.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://console.cloud.google.com/&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;Google 클라우드 플랫폼&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;로그인 Google 클라우드 플랫폼으로 이동&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;accounts.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 GCP에 가입을 해주세요. 가입은 gcp에서 제공하는 절차대로 해주시면 됩니다. 결제에 대한 부분은 크레딧이 모두 사용되고 나면 자동결제로 넘어가지는 않기 때문에 형식적으로 기재만 해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 vm인스턴스를 만들기 이전에 프로젝트를 만드셔야할텐데요. vm 인스턴스가 특정 프로젝트에 속한다고 생각하시면 될것 같습니다. 저는 이미 만들어져있는 프로젝트가 있어서 gcp에서 안내해주는대로 프로젝트를 간단히 만들어주시면 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Compute Engine -&amp;gt; VM 인스턴스&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;1698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMjgnc/btr1pYan0e4/ZYguXTiD3Q4ZdpXubZREZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMjgnc/btr1pYan0e4/ZYguXTiD3Q4ZdpXubZREZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMjgnc/btr1pYan0e4/ZYguXTiD3Q4ZdpXubZREZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMjgnc%2Fbtr1pYan0e4%2FZYguXTiD3Q4ZdpXubZREZk%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;400&quot; height=&quot;710&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;1698&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 에서는 EC2라는 서비스로 알려져있으며 물리서버 한대를 빌린다고 생각하시면 됩니다. 왼쪽 위 메뉴에서 Compute Engine을 찾고 VM인스턴스로 넘어갑니다.&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignLeft&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/dNftKx/btr1ueKLh0z/WlDn0kpc53dUVsoM6a3QMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNftKx/btr1ueKLh0z/WlDn0kpc53dUVsoM6a3QMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNftKx/btr1ueKLh0z/WlDn0kpc53dUVsoM6a3QMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNftKx%2Fbtr1ueKLh0z%2FWlDn0kpc53dUVsoM6a3QMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;210&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 인스턴스 명 + 리전/영역 선택&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lfcz3/btr1IK9ypJO/Op9SACXzqKIVjbKdPzapSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lfcz3/btr1IK9ypJO/Op9SACXzqKIVjbKdPzapSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lfcz3/btr1IK9ypJO/Op9SACXzqKIVjbKdPzapSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flfcz3%2Fbtr1IK9ypJO%2FOp9SACXzqKIVjbKdPzapSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;458&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 인스턴스의 이름과 리전 및 영역을 선택해줍니다. 인스턴스의 이름은 말그대로 가상머신의 이름을 지어주면 됩니다. 저의 경우는 elasticsearch 세미나를 진행해보기 위해서 만들 vm 인스턴스이기 때문에 blackdog-elasticsearch-1로 짓겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리전은 물리 데이터센터가 운영되는 위치인데요. 대한민국에는 서울 한군데에서 세가지 영역에서 운영하고 있는 것으로 알고 있습니다. 한국에서 접속해서 하고 있으니 어쨌든 서울과 영역 a를 선택해줍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 머신 구성, 부팅 디스크(os 선택), 방화벽(HTTP, HTTPS)&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bShN16/btr1p2XRi0y/49FSmIOr69fkiLROZXmHD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bShN16/btr1p2XRi0y/49FSmIOr69fkiLROZXmHD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bShN16/btr1p2XRi0y/49FSmIOr69fkiLROZXmHD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbShN16%2Fbtr1p2XRi0y%2F49FSmIOr69fkiLROZXmHD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;654&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 머신구성입니다. 머신을 어떤 용도로 사용할지에 따라서 각 상품이 다르게 적용되어있고 저는 단순히 elasticsearch 하나만 올릴 서버가 필요할 뿐이니 e2-medium 스펙을 선택해보겠습니다.(여기서 무료 크레딧이 아까우실 수 있으니 단순 vm 인스턴스 생성이 목적이신분들은 보다 작은 서버를 만드시거나 반드시 본인의 용도에 맞는 스펙을 선택하세요.)&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2208&quot; data-origin-height=&quot;1270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Geu2H/btr1uexfCYP/jiyMSWpENOcGVF2CkY6Y01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Geu2H/btr1uexfCYP/jiyMSWpENOcGVF2CkY6Y01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Geu2H/btr1uexfCYP/jiyMSWpENOcGVF2CkY6Y01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGeu2H%2Fbtr1uexfCYP%2FjiyMSWpENOcGVF2CkY6Y01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;345&quot; data-origin-width=&quot;2208&quot; data-origin-height=&quot;1270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 부팅디스크 영역에서 기본으로 머신 이미지가 Debian으로 선택되어 있을 겁니다. 저는 centOS에서 elasticsearch를 실행할 것이기 때문에 이를 변경해줍니다. 원하시는 OS에 따라서 Debian으로 그대로 선택하거나 다른 OS로 변경을 해줍니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y4iEO/btr1IKIwzC4/cLkz0EuVzdiJTPuzoaTyFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y4iEO/btr1IKIwzC4/cLkz0EuVzdiJTPuzoaTyFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y4iEO/btr1IKIwzC4/cLkz0EuVzdiJTPuzoaTyFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy4iEO%2Fbtr1IKIwzC4%2FcLkz0EuVzdiJTPuzoaTyFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;232&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음은 방화벽 영역에서 HTTP와 HTTPS 트래픽 허용처리하겠습니다. 해당 처리가 되어있지 않다면 gcp에서 기본적으로 포트에 대한 보안처리가 all block 처리가 되어있을거기 때문에 해당 서버로 들어오는 외부 http 혹은 https 요청을 받을 수 없습니다. 그래서 저는 둘다 허용으로 하겠습니다.&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;b&gt;5. 더 필요한 부분이 없는지 체크.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여기서 더 필요한 사항이 없는지 체크합니다. &lt;u&gt;고급옵션&lt;/u&gt;에 여러가지 옵션들이 있는데요. 이 부분은 gcp를 많이 사용해보지 않아서 정확히 필요한지도 모르겠어서 일단 패스합니다. 그 외에도 제가 세미나를 위해 elasticsearch를 설치하기 위한 vm에 필요한 옵션들은 없는 것같아서 &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;vm 목록으로 돌아온 후 blackdog-elasticsearch-1 vm이 상태가 무언가 돌아가고 있을겁니다. green light가 들어올때까지 기다려줍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 생성된 정보 확인.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 인스턴스가 정상적으로 생성되었습니다. 기존에 입력했던 대로 vm이 생성되었는지 확인해보시기 바랍니다.&lt;/p&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>GCP</category>
      <category>Compute Engine VM</category>
      <category>GCP</category>
      <category>GCP create VM</category>
      <category>gcp vm 만들기</category>
      <category>Google Cloud</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/489</guid>
      <comments>https://sas-study.tistory.com/489#entry489comment</comments>
      <pubDate>Thu, 2 Mar 2023 23:23:48 +0900</pubDate>
    </item>
    <item>
      <title>[AWS 연결오류] Permissions 0644 for 'pem' are too open</title>
      <link>https://sas-study.tistory.com/488</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 서비스로 신규 nexus 서버를 추가하기 위해 사내 aws 권한중 ec2 관련 권한을 받아 서버 인스턴스를 구축중에 위와 같은 에러를 만났습니다.&lt;/p&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;pre id=&quot;code_1677043696502&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WARNING: UNPROTECTED PRIVATE KEY FILE!

Permissions 0644 for 'nexus.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key &quot;nexus.pem&quot;: bad permissions
ec2-user@ec2-n-nn-nn-n.ap-northeast-2.compute.amazonaws.com: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).&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;내용을 훑어 봤을때는 신규로 발급받은 pem 키에 대한 open이 너무 과하게 열려있다는 뜻 같은데.. 사실 영문이라 무슨 뜻인 줄 확실히 알지는 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀더 서칭을 해봤고 관련 키워드 및 해결방법을 정리한 stackoverflow를 확인하고 나서 open에 대한 의미를 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;open이란 단어를 파일의 접근 권한으로 생각하면 authorization으로 번역이 될 수 있었다. aws에서 생각보다 추상적인 워딩으로 쓴건지.. 초다국적 기업이기 때문에 쉬운 용어로 접근하기위해서 작성한건지.. 내가 영어가 그냥 꽝인건지..ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 단순히 pem 파일의 권한을 아래와 같이 수정해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1677043931020&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;chmod 400 nexus.pem&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;&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;&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;&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;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://stackoverflow.com/questions/29933918/ssh-key-permissions-0644-for-id-rsa-pub-are-too-open-on-mac&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/29933918/ssh-key-permissions-0644-for-id-rsa-pub-are-too-open-on-mac&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677043963900&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;SSH Key: &amp;ldquo;Permissions 0644 for 'id_rsa.pub' are too open.&amp;rdquo; on mac&quot; data-og-description=&quot;I generate a ssh key pair on my mac and add the public key to my ubuntu server(in fact, it is a virtual machine on my mac),but when I try to login the ubuntu server,it says: @@@@@@@@@@@@@@@@@@@@@@...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/29933918/ssh-key-permissions-0644-for-id-rsa-pub-are-too-open-on-mac&quot; data-og-url=&quot;https://stackoverflow.com/questions/29933918/ssh-key-permissions-0644-for-id-rsa-pub-are-too-open-on-mac&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eb35J3/hyRIt222Ie/Zltckop23kCGLTVncuTxPk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/29933918/ssh-key-permissions-0644-for-id-rsa-pub-are-too-open-on-mac&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/29933918/ssh-key-permissions-0644-for-id-rsa-pub-are-too-open-on-mac&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eb35J3/hyRIt222Ie/Zltckop23kCGLTVncuTxPk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;SSH Key: &amp;ldquo;Permissions 0644 for 'id_rsa.pub' are too open.&amp;rdquo; on mac&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I generate a ssh key pair on my mac and add the public key to my ubuntu server(in fact, it is a virtual machine on my mac),but when I try to login the ubuntu server,it says: @@@@@@@@@@@@@@@@@@@@@@...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.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>Amazon WebService</category>
      <category>ec2 aws 연결 오류</category>
      <category>Permissions 0644 aws</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/488</guid>
      <comments>https://sas-study.tistory.com/488#entry488comment</comments>
      <pubDate>Wed, 22 Feb 2023 14:34:11 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] Mac OS Redis 로컬 설치 및 간단한 명령어 사용법</title>
      <link>https://sas-study.tistory.com/487</link>
      <description>&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;간단히 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는 Remote Dictionary Server의 약자로써 key - value 형태로 데이터를 저장하는 NoSQL의 한 종류입니다.&lt;/p&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;brew를 통한 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mac은 brew를 통해 쉽게 redis를 설치할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;brew install redis&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;blockquote data-ke-style=&quot;style3&quot;&gt;homebrew-core is a shallow clone&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법 : &lt;a href=&quot;https://sas-study.tistory.com/486&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/486&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1666767146806&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;[homebrew] homebrew-core is a shallow clone 에러 해결.&quot; data-og-description=&quot;Mac OS 로컬환경에 Redis를 설치하는 도중 home brew update를 진행하고자 brew update 명령문을 날리는 순간. Error: homebrew-core is a shallow clone. To &amp;#96;brew update&amp;#96;, first run: git -C /usr/local/Homebr..&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/486&quot; data-og-url=&quot;https://sas-study.tistory.com/486&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/C4Jy5/hyQlv2UJhp/SHQZuGYsVKqGQ3UMkKvtH1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ccbOU5/hyQlBWn41S/dedeaiMC9yCoTKKXMRTj8k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bYMM1F/hyQlHhYF3T/GdtT2e6rxlLobwhjLq7vrK/img.jpg?width=910&amp;amp;height=512&amp;amp;face=0_0_910_512&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/486&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/486&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/C4Jy5/hyQlv2UJhp/SHQZuGYsVKqGQ3UMkKvtH1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ccbOU5/hyQlBWn41S/dedeaiMC9yCoTKKXMRTj8k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bYMM1F/hyQlHhYF3T/GdtT2e6rxlLobwhjLq7vrK/img.jpg?width=910&amp;amp;height=512&amp;amp;face=0_0_910_512');&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;[homebrew] homebrew-core is a shallow clone 에러 해결.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Mac OS 로컬환경에 Redis를 설치하는 도중 home brew update를 진행하고자 brew update 명령문을 날리는 순간. Error: homebrew-core is a shallow clone. To `brew update`, first run: git -C /usr/local/Homebr..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sas-study.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;pre id=&quot;code_1666767181615&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services start redis&lt;/code&gt;&lt;/pre&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에 대해서 종료 혹은 재부팅을 위해서는 아래의 명령어를 입력할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666767258341&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services stop redis
brew services restart redis&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;그리고 redis cli에 접속하기위해 아래의 명령어를 입력해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666767280303&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;redis-cli&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;이제 로컬 terminal로 redis에 데이터를 쓰고 읽어볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Redis CRUD&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 redis crud를 진행해보겠습니다.&lt;/p&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;u&gt;1) 데이터 삽입&lt;/u&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;gt;&amp;nbsp; &lt;b&gt;set&lt;/b&gt; key &lt;b&gt;value&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;key&quot;라는 문자열로 &quot;value&quot;라는 문자열을 저장합니다.&lt;/p&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;u&gt;2) 데이터 조회&lt;/u&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;gt;&amp;nbsp; keys *&lt;br /&gt;&lt;br /&gt;&amp;gt; 결과&lt;br /&gt;&amp;gt; 1) &quot;key&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keys * 라는 명령어로 전체 key 목록을 출력해보았습니다.(array를 리턴합니다.) 방금 저장한 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;이건 사실 &lt;b&gt;keys [패턴]&amp;nbsp;&lt;/b&gt;문법을 활용한 것입니다.&amp;nbsp; 예컨데 keys ke* 로 검색해도 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;이번엔 단건을 조회해보겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;gt;&amp;nbsp; get key&lt;br /&gt;&lt;br /&gt;&amp;gt;&amp;nbsp; 결과&lt;br /&gt;&amp;gt;&amp;nbsp; &quot;value&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;get [key 명]&lt;/b&gt;을 통해 해당 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;&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;u&gt;3) 데이터 삭제&lt;/u&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;gt;&amp;nbsp; del key&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;del [key 명]&lt;/b&gt;을 통해 해당 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;&lt;u&gt;4) 데이터 수정&lt;/u&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;gt;&amp;nbsp; rename key newKey&lt;br /&gt;&lt;br /&gt;&amp;gt;&amp;nbsp; get newKey&lt;br /&gt;&amp;gt; 결과&lt;br /&gt;&amp;gt; &quot;value&quot;&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;해당 명령어를 통해 key를 변경할 수 있습니다. &quot;key&quot;를 &quot;newKey&quot;로 변경하여 다시 newKey라는 키로 조회하면 &quot;value&quot;값이 조회됩니다.&lt;/p&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;u&gt;덮어쓰기&lt;/u&gt;처럼 데이터 수정을 할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;gt; set newKey value2&lt;br /&gt;&lt;br /&gt;&amp;gt; get newKey&lt;br /&gt;&lt;br /&gt;&amp;gt; 결과&lt;br /&gt;&amp;gt; &quot;value2&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Redis</category>
      <category>redis crud</category>
      <category>레디스 기본 예제</category>
      <category>레디스 예제</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/487</guid>
      <comments>https://sas-study.tistory.com/487#entry487comment</comments>
      <pubDate>Wed, 26 Oct 2022 16:05:35 +0900</pubDate>
    </item>
    <item>
      <title>[homebrew] homebrew-core is a shallow clone 에러 해결.</title>
      <link>https://sas-study.tistory.com/486</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Mac OS 로컬환경에 Redis를 설치하는 도중 home brew update를 진행하고자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;brew update 명령문을 날리는 순간.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Error:&amp;nbsp;&lt;br /&gt;&amp;nbsp; homebrew-core is a shallow clone.&lt;br /&gt;To `brew update`, first run:&lt;br /&gt;&amp;nbsp; git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow &lt;br /&gt;This command may take a few minutes to run due to the large size of the repository. This restriction has been made on GitHub's request because updating shallow clones is an extremely expensive operation due to the tree layout and traffic of Homebrew/homebrew-core and Homebrew/homebrew-cask. We don't do this for you automatically to avoid repeatedly performing an expensive unshallow operation in CI systems (which should instead be fixed to not use shallow clones). Sorry for the inconvenience!&lt;/blockquote&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;blockquote data-ke-style=&quot;style3&quot;&gt;git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow&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;해당 명령어를 terminal cli 에 입력하고 brew update 명령어를 입력했더니 무사히 진행되었다.&lt;/p&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 또한 무사히 로컬에 설치할 수 있었따. (생각보다 친절한 error 메시지였따..)&lt;/p&gt;</description>
      <category>에러모음</category>
      <category>brew update 에러</category>
      <category>home brew</category>
      <category>homebrew update</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/486</guid>
      <comments>https://sas-study.tistory.com/486#entry486comment</comments>
      <pubDate>Wed, 26 Oct 2022 15:41:29 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] 코틀린 제네릭 (Generics)</title>
      <link>https://sas-study.tistory.com/485</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;제네릭&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭은 컴파일 시점에 해당 타입을 확정짓지않고 런타임으로 유예시켜 다양한 타입을 받아들일 수 있도록 제공되는 문법입니다. 자바에서 Collection(List, Map, Set 등)을 활용해보셨다면 코틀린에서도 충분히 쉽게 다가올 수 있을 것입니다.&lt;/p&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;u&gt;타입 파라미터&lt;/u&gt;를 가질 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class BlackdogGenerics&amp;lt;T&amp;gt;(val t: T) {
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;타입 파라미터란&lt;/u&gt; 위의 제네릭 클래스의 T 타입입니다.&lt;/p&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;u&gt;타입 아규먼트&lt;/u&gt;를 제공해주면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
	val blackdogGenerics = BlackdogGenerics&amp;lt;String&amp;gt;(&quot;제네릭&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;u&gt;타입 아규먼트 선언은 생략&lt;/u&gt;할 수 있습니다. (&lt;b&gt;타입추론&lt;/b&gt;)&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val blackdogGenerics = BlackdogGenerics(&quot;제네릭&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입추론이 가능하기 때문에 &lt;u&gt;변수의 타입&lt;/u&gt;에 타입 아규먼트를 추가하거나 타입 아규먼트를 &lt;u&gt;생성자&lt;/u&gt;에 표시할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val list1: MutableList&amp;lt;String&amp;gt; = mutableListOf()
    val list2 = mutableListOf&amp;lt;String&amp;gt;()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떠한 타입이 들어올 지 예상할 수 없는 경우, 안전하게 사용하기 위하여 스타 프로젝션 구문(*)을 제공합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val list: List&amp;lt;*&amp;gt; = listOf&amp;lt;String&amp;gt;(&quot;테스트&quot;)
    val listInt: List&amp;lt;*&amp;gt; = listOf&amp;lt;Int&amp;gt;(1, 2, 3);
    val listDouble: List&amp;lt;*&amp;gt; = listOf&amp;lt;Double&amp;gt;(1.1, 2.2, 3.3);
    println(list)
    println(listInt)
    println(listDouble)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;변성&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제네릭에서 파라미터화된 타입이 서로 어떤 관계에 있는지 설명하는 개념.&lt;/li&gt;
&lt;li&gt;공변성, 반공변성, 무공변성으로 나뉜다.&lt;/li&gt;
&lt;li&gt;이펙티브 자바에서는 공변성과 반공변성에 대해서 이야기할 때, PECS라는 규칙을 언급한다.&lt;/li&gt;
&lt;li&gt;PECS 는 Producer - Extends / Consumer - Super 의 약자입니다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;공변성은 자바의 &lt;u&gt;extends&lt;/u&gt; 키워드이고 코틀린에서는 &lt;b&gt;out&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;반공변성은 자바의 &lt;u&gt;super&lt;/u&gt; 키워드이고 코틀린에서는 &lt;b&gt;in&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공변성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공변성을 활용하지 않아서 문제가 되는 케이스&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class BlackdogGenerics&amp;lt;T&amp;gt;(val t: T) { }

fun main() {
    val generics = BlackdogGenerics(&quot;테스트&quot;);
    val charGenerics: BlackdogGenerics&amp;lt;CharSequence&amp;gt; = generics; // 컴파일 오류
}&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;현재 코드에서는 실수로 String이 아닌 CharSequence 타입을 대입하게 되는 경우 컴파일 오류가 발생할 수 있다. 이에 대해서 허용을 해주기 위해서는 &lt;u&gt;공변성 키워드인 &lt;b&gt;out&lt;/b&gt;을 활용해야한다.&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class BlackdogGenerics&amp;lt;out T&amp;gt;(val t: T) { }

fun main() {
    val generics = BlackdogGenerics(&quot;테스트&quot;);
    val charGenerics: BlackdogGenerics&amp;lt;CharSequence&amp;gt; = generics;// 컴파일 성공.
}&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;&amp;lt;out T&amp;gt; 키워드는 자바에서 &amp;lt;? extends T&amp;gt;와 같은 문법으로 T가 CharSequence이기 때문에 그 하위타입인 제네릭 String 타입이 컴파일 될 수 있도록 코틀린 컴파일러가 이에 대한 거부를 하지 않습니다.&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;b&gt;반공변성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반공변성을 활용하지 않아서 문제가 되는 케이스&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class Bag&amp;lt;T&amp;gt; {
    fun saveAll(to: MutableList&amp;lt;T&amp;gt;, from: MutableList&amp;lt;T&amp;gt;) {
        to.addAll(from)
    }
}

fun main() {
    val bag = Bag&amp;lt;String&amp;gt;()

    // in이 없다면 컴파일 오류
    bag.saveAll(mutableListOf&amp;lt;CharSequence&amp;gt;(&quot;CharSequence&quot;), mutableListOf&amp;lt;String&amp;gt;(&quot;String&quot;))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Bag&amp;lt;T&amp;gt; 타입의 클래스는 파라미터로 전달된 타입은 to, from 양쪽 모두 String입니다. 하지만 to 파라미터에 전달된 아규먼트는 CharSequence타입을 인자로 받으려고 하기 때문에 컴파일러는 이를 거부합니다. &lt;u&gt;(위의 공변성 예와 동일한 케이스입니다.)&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class Bag&amp;lt;T&amp;gt; {
    fun saveAll(to: MutableList&amp;lt;in T&amp;gt;, from: MutableList&amp;lt;T&amp;gt;) {
        to.addAll(from)
    }
}

fun main() {
    val bag = Bag&amp;lt;String&amp;gt;()

    // in이 없다면 컴파일 오류
    bag.saveAll(mutableListOf&amp;lt;CharSequence&amp;gt;(&quot;CharSequence&quot;), mutableListOf&amp;lt;String&amp;gt;(&quot;String&quot;))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 반공변성 키워드인 in을 사용해주면 상위 타입도 전달받을 수 있도록 처리됩니다. in 키워드는 자바에서 &amp;lt;? super T&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;u&gt;&lt;b&gt;무공변성&lt;/b&gt;&lt;/u&gt;으로써 in, out 어떤 것도 지정하지 않은 경우입니다. 이는 List&amp;lt;String&amp;gt;과 List&amp;lt;CharSequence&amp;gt;는 그 어떤 관계또 없음을 나타냅니다.&lt;/p&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <category>코틀린 Generics</category>
      <category>코틀린 공변성 반공변성</category>
      <category>코틀린 제네릭</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/485</guid>
      <comments>https://sas-study.tistory.com/485#entry485comment</comments>
      <pubDate>Tue, 25 Oct 2022 15:17:15 +0900</pubDate>
    </item>
    <item>
      <title>공변성과 반공변성(Java, Kotlin Covariance / Contravariance)</title>
      <link>https://sas-study.tistory.com/484</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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Array&lt;span&gt;는&lt;/span&gt; &lt;span&gt;공변성이다&lt;/span&gt;.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 기본적으로 공변성입니다. 예를 들어, T[] 배열은 요소로써 &lt;u&gt;T 하위 타입을 삽입할 수 있는 규칙이 허용&lt;/u&gt;된다는 뜻입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    Number[] numbers = new Number[4];
    numbers[0] = 10;
    numbers[1] = 3.14;
    numbers[2] = 11L;
    numbers[3] = 3.25f;
}&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;타입 S가 타입 T의 하위타입일 때, S[] 타입 또한&amp;nbsp; T[] 타입의 하위타입이 성립됩니다. 따라서 아래의 코드 또한 문제없이 동작합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    Integer[] myInts = {1,2,3,4};
    Number[] myNumber = myInts;
}&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;Java에서의 하위 타입 규칙에 따라 Integer가 Number의 하위타입이기 때문에 배열 Integer[]는 배열 Number[]의 하위타입이 됩니다.&lt;/p&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

myNumber[0] = 3.14; // heap pollution&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storing&amp;nbsp;element&amp;nbsp;of&amp;nbsp;type&amp;nbsp;'java.lang.Double'&amp;nbsp;to&amp;nbsp;array&amp;nbsp;of&amp;nbsp;'java.lang.Integer'&amp;nbsp;elements&amp;nbsp;will&amp;nbsp;produce&amp;nbsp;'ArrayStoreException'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 라인은 컴파일은 무사히 되지만 코드를 실행하게 된다면 Integer 배열에 Double 값을 넣으려고 하기 때문에 ArrayStoreException이 발생하게 됩니다. 여기서 myNumber라는 배열이 Number 타입으로 선언되어있고 그 하위 타입인 Double이 무사히 캐스팅 된다는 사실은 중요하지 않습니다. 이미 Number[] 타입은 Integer[] 인스턴스를 포함하고 있고 여기에 Double을 요소로 삽입하고자 한다는 행위가 문제가 됩니다.&lt;/p&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;reifiable type&lt;/b&gt; (&lt;a href=&quot;https://cla9.tistory.com/52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://cla9.tistory.com/52&lt;/a&gt;)이라고 부릅니다. 런타임 시스템이 Java가 이 배열이 실제로 단순히 Number[] 타입의 참조를 통해 엑세스되는 Integer[] 타입으로 인스턴스화 되었음을 알고 있음을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 우리가 볼 수 있듯이 하나는(Integer[]) 객체의 실제 유형이고 다른 하나(Number[])는 엑세스하는데 사용하는 참조 유형입니다.&lt;/p&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Java의 Generic에서의 문제점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Java의 Generic 에서의 문제점은 코드 컴파일이 완료된 이후 Type Parameter는 컴파일러에 의해 삭제된다는 것입니다. 따라서 이 타입에 대한 정보는 런타임에서 사용할 수 없다는 것입니다. 이러한 내용을 &lt;b&gt;Type Erasure&amp;nbsp;&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;여기서 중요한 것은 런타임에 타입정보를 가지고 있지 않기 때문에 위에처럼 heap pollution을 저지르지 않도록 하는 방법이 없다는 것입니다.&lt;/p&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 class=&quot;cs&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    List&amp;lt;Integer&amp;gt; myInts = new ArrayList&amp;lt;Integer&amp;gt;();
    myInts.add(1);
    myInts.add(2);
    List&amp;lt;Number&amp;gt; myNums = myInts; //compiler error
    myNums.add(3.14); //heap polution
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 컴파일러가 위의 행위를 막지 못한다면.. 런타임 타입 시스템도 이것을 막을 방법이 없습니다. 왜냐하면 런타임에 이 목록이 Integer로만 이루어져야한다는 결정할 타입정보를 갖지 못하기 때문입니다. 런타임 시스템은 Integer만 포함해야 할 때 이 list에 넣을 수 있도록 합니다. 생성될 때 애초에 Integer로 선언되었기 때문입니다. 따라서 컴파일러에서 main 메소드의 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;따라서 Generic에서는 컴파일러가 배열처럼 타입을 속이는 행위를 하지 못하도록 했습니다.&amp;nbsp; 따라서 런타임에 Generic 타입의 진짜 타입을 결정할 수 없기 때문에 구체화할 수 없다고 말합니다.&lt;/p&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의 제네릭 타입에 대한 특징은 다형성에 부정적인 영향을 끼칩니다. 아래의 예로 확인해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    Integer[] myInts = {1,2,3,4,5};
    Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
    Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};
    System.out.println(sum(myInts)); // 15
    System.out.println(sum(myLongs)); // 15
    System.out.println(sum(myDoubles)); // 15
}

static long sum(Number[] numbers) {
    long summation = 0;
    for(Number number : numbers) {
        summation += number.longValue();
    }
    return summation;
}&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;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    List&amp;lt;Integer&amp;gt; myInts = asList(1,2,3,4,5);
    List&amp;lt;Long&amp;gt; myLongs = asList(1L, 2L, 3L, 4L, 5L);
    List&amp;lt;Double&amp;gt; myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);
    System.out.println(sum(myInts)); //compiler error
    System.out.println(sum(myLongs)); //compiler error
    System.out.println(sum(myDoubles)); //compiler error

}

static long sum(List&amp;lt;Number&amp;gt; numbers) {
    long summation = 0;
    for(Number number : numbers) {
        summation += number.longValue();
    }
    return summation;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이번엔 Collection Generic을 활용해서 처리해보겠습니다. 해당 코드는 세번의 함수 사용이 모두 컴파일 오류를 생성하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해서 Integer 리스트를 Number 리스트의 하위 타입으로 간주할 수 없다는 것입니다. 이러한 행위가 시스템에 안전하지 않은 것으로 간주되어 컴파일러가 오류를 뱉습니다.&lt;/p&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;u&gt;&lt;b&gt;다형성&lt;/b&gt;에 대한 Java의 이점을 활용하지 못하도록 되고있습니다&lt;/u&gt;. 어떻게든 수정이 필요해보입니다. 이러한 문제를 해결하는 방법으로 &lt;b&gt;공변성&lt;/b&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;공변성(Covariance)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 타입 &amp;lt;T&amp;gt;를 사용하는 대신에 &amp;lt;? extends T&amp;gt; 를 사용할 수 있습니다. 이러한 공변성을 활용하면 구조(제네릭 구조)에서 항목(타입)을 읽을 수 있지만 아무것도 쓸 수는 없습니다.(cannot write) 아래의 내용은 모두 유효한 공변선언입니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;List&amp;lt;? extends Number&amp;gt; myNums1 = new ArrayList&amp;lt;Integer&amp;gt;();
List&amp;lt;? extends Number&amp;gt; myNums2 = new ArrayList&amp;lt;Float&amp;gt;();
List&amp;lt;? extends Number&amp;gt; myNums3 = new ArrayList&amp;lt;Double&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;그리고 다음을 수행하여 제네릭 구조 myNums에서 읽을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Number n = myNums.get(0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;왜냐하면 실제 list에는 어떤 값이 들어있든지 Number 타입으로 업캐스팅이 가능하다는 것을 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 공변성 구조는 아래의 행위를 허용하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    List&amp;lt;? extends Number&amp;gt; myNums1 = new ArrayList&amp;lt;Integer&amp;gt;();
    myNums1.add(Integer.valueOf(1)); // compile error
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 컴파일러가 실제 제네릭 구조 내에 있는 요소의 실제 타입을 결정할 수 없기 때문에 허용되지 않습니다. Number를 상속하는 모든 타입(Integer, Double 등등의 숫자 타입)일 수는 있지만 컴파일러는 무엇인지 확신할 수 없으므로 제네릭 값을 검색하는 모든 행위를 안전하지 않은 작업으로 간주되며 컴파일러에 의해 즉시 거부됩니다.&lt;/p&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;u&gt;따라서 우리는 읽을 수는 있지만 쓸수는 없습니다.(can read, cannot write)&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;반공변성(Contravariance)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반공변성을 위해 &amp;lt;T&amp;gt; 타입이 아닌 &amp;lt;? super T&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;이 경우 객체의 실제 속성은 List 유형의 Object 이며 반공변성을 통해 Number를 넣을 수 있습니다. 기본적으로 Number는 공통조상이 Object이기 때문입니다. 따라서 모든 숫자도 객체이므로 유효합니다.&lt;/p&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 class=&quot;typescript&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    List&amp;lt;? super Number&amp;gt; myNums = new ArrayList&amp;lt;&amp;gt;();
    Number n = myNums.get(0); //compiler-error
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러가 이 라인을 작성할 수 있도록 허용한다면 런타임에 ClassCaseException이 발생할 여지가 있습니다. 따라서 다시한번 컴파일러는 이 안전하지 않은 작업을 허용할 위험이 없으며 즉시 거부합니다. &lt;u&gt;(&amp;lt;? super Number&amp;gt; 의 경우 최대한 안전하게 읽기 위해서는 최상위 클래스인 Object로 받아야만 ClassCaseException을 피할 수 있을 것입니다. 따라서 불안전한 모든 타입의 경우 컴파일을 거부하는게 합리적인 것 같습니다.)&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Get / Put 원리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하자면 구조에서 제네릭 값만 가져오기 위해서는 공변성을 사용합니다.(? extends T) 제네릭 값을 구조에 넣을 때만 반공변성을 사용하고 둘 다 수행하려는 경우 무공변성(invariant)을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    List&amp;lt;Integer&amp;gt; myInts = asList(1,2,3,4);
    List&amp;lt;Double&amp;gt; myDoubles = asList(3.14, 6.28);
    List&amp;lt;Object&amp;gt; myObjs = new ArrayList&amp;lt;Object&amp;gt;();
    copy(myInts, myObjs);
    copy(myDoubles, myObjs);
}

public static void copy(List&amp;lt;? extends Number&amp;gt; source,
                        List&amp;lt;? super Number&amp;gt; destiny) {
    for(Number number : source) {
        destiny.add(number);
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 공변성과 반공변성의 예제로 가장 적절한 예시는 위와 같습니다. 한 목록에서 다른 목록으로 모든 종류의 숫자를 복사하는 예제입니다. source에서만 요소를 읽고 destiny에만 요소를 넣습니다. 공변성과 반공변성의 특징덕분에 위와 같은 경우 효과가 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reference&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dzone.com/articles/covariance-and-contravariance&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dzone.com/articles/covariance-and-contravariance&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;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>흑구의 공부내용 공유</category>
      <category>? extends T</category>
      <category>? super T</category>
      <category>extends</category>
      <category>Generics</category>
      <category>super</category>
      <category>공변성</category>
      <category>공변성 반공변성</category>
      <category>반공변성</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/484</guid>
      <comments>https://sas-study.tistory.com/484#entry484comment</comments>
      <pubDate>Fri, 21 Oct 2022 17:25:34 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] 코틀린 확장함수</title>
      <link>https://sas-study.tistory.com/483</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;확장함수란&lt;/b&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;예를 들어, String 클래스를 예로 String 인스턴스의 맨 첫번째 요소를 가져오는 함수를 만들어두고 표준라이브러리에 추가하는 효과처럼 보이고 싶다면&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun String.getFirstWord() = this[0]

fun main() {
    val str = &quot;str&quot;;
    println(str.getFirstWord())
}
// s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 마치 String 클래스에 이어서 getFirstWord라는 함수를 추가해두고 필요할 때 호출해서 사용할 수 있다. main 함수에서는 마치 이미 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;this 키워드는 String 클래스 타입의 인스턴스를 나타내고 main 함수 내에서는 &quot;str&quot;이라는 문자열을 지칭하게 된다.&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;인텔리제이의 kotlin bytecode 기능을 이용해서 해당 코드를 디컴파일 해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public final class _5_ExtensionKt {
   public static final char getFirstWord(@NotNull String $this$getFirstWord) {
      Intrinsics.checkNotNullParameter($this$getFirstWord, &quot;$this$getFirstWord&quot;);
      return $this$getFirstWord.charAt(0);
   }

   public static final void main() {
      String str = &quot;str&quot;;
      char var1 = getFirstWord(str);
      System.out.println(var1);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바로 코드를 변경해보니 psvm 메인 메소드가 별도로 존재하고 코틀린의 main 함수를 호출하고 있다. 그런데 getFirstWord 메소드를 보면 내용이 내가 구현했던 this[0] 과 구현부가 똑같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 charAt(0) 이라는 구문을 통해 첫번째 문자열을 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&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;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public final class _5_Extension_exKt {
   public static final void main() {
      String str = &quot;str&quot;;
      char var1 = _5_ExtensionKt.getFirstWord(str);
      System.out.println(var1);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getFirstWord 메소드가 선언된 코틀린파일인 _5_ExtensionKt (위에서 컴파일된 클래스임) 에서 static으로 호출한다.&lt;/p&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;선언한 클래스파일의 static 메소드일 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;클래스의 static 메소드라면 만약 동일한 메소드 시그니처를 가진 두개의 확장함수가 생긴다면 어떻게될까??&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;Conflicting overloads 라는 키워드를 통해서 코틀린 컴파일러 내에서 컴파일 에러를 보여준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;오버로드 해결이 모호합니다. 이 모든 함수가 일치합니다. &lt;br /&gt;- public fun String.getFirstWord(): Char defined in my.심화 in file 5_Extension2.kt &lt;br /&gt;- public fun String.getFirstWord(): Char defined in my.심화 in file 5_Extension.kt&lt;/blockquote&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>프로그래밍 언어/Kotlin</category>
      <category>kotlin extension</category>
      <category>코틀린 확장함수</category>
      <category>확장함수</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/483</guid>
      <comments>https://sas-study.tistory.com/483#entry483comment</comments>
      <pubDate>Wed, 19 Oct 2022 20:34:34 +0900</pubDate>
    </item>
    <item>
      <title>HTTPS는 도대체 무슨일을 하나요??</title>
      <link>https://sas-study.tistory.com/482</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HTTPS가 필요한 이유&lt;/b&gt;&lt;/h3&gt;
&lt;ol 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;/ol&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버간 메시지 전송 시 통신하는 서버가 아닌 &lt;u&gt;제 3자가 이를 가로챌 수 없음&lt;/u&gt;을 의미한다.&lt;/li&gt;
&lt;li&gt;이러한 암호화되지 않은 메시지가 제 3자에게 노출된다면 잠재적으로 악용될 소지가 있음을 나타낸다.&lt;/li&gt;
&lt;li&gt;HTTPS 없이 웹사이트를 탐색한다면 비밀번호 등 &lt;u&gt;&lt;b&gt;유효정보를 도청당할 수 있다.&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;브라우저의 URL 표시줄에 자물쇠는 이렇게 몰래 훔쳐보고 있는 제 3자가 없다는 것을 뜻한다.&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;무결성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무결성은 메시지가 목적지로 가는 도중에 &lt;u&gt;&lt;b&gt;조작되지 않음&lt;/b&gt;&lt;/u&gt;을 의미한다.&lt;/li&gt;
&lt;li&gt;예를 들어, 내가 누군가에게 메시지를 좋은 말로 보냈는데 제 3자에 의해 수정되어 못된 말로 변경되어 가는 경우 이러한 무결성이 보장되지 못한다고 볼 수있다.&lt;/li&gt;
&lt;li&gt;위의 예처럼 중간에서 조작되는 것을 &lt;b&gt;중간자 공격&lt;/b&gt;이라고 한다.&lt;/li&gt;
&lt;li&gt;따라서 HTTPS는 무결성을 위해 메시지가 변형되지 않았음을 보장한다.&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;식별&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;피싱사이트의 경우, 이러한 식별되지 않은 사이트에 접근하여 누구나 생각하는 신뢰있는 그 사이트의 정보를 해킹하기 위한 수단인데 HTTPS는 이러한 식별을 도와준다.&lt;/li&gt;
&lt;li&gt;실제 클라이언트가 요청시 서버는 클라이언트에게 자신의 공인된 디지털 서명을 첨부해주는데 이것이 바로 SSL 인증서이다.&lt;/li&gt;
&lt;li&gt;브라우저 내에 이러한 인증서를 관장하는 CA라는 정보기관들의 목록이 내장되어 있는데 이를 통해 해당 서버에 접근한 클라이언트가 자신이 맞는 서버에 접근했는지를 증명해줄 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;대칭키와 비대칭키 알고리즘&lt;/b&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;HTTPS는 웹에서 개인정보보호, 무결성 및 식별 기능을 제공하기 위한 방법이 필요로 하게 됐는데 이에 대한 바탕이 암호화이다. 이런 암호화는 두가지 유형의 암호화가 있는데 하나는 대칭키 방식, 또하나는 비대칭키(공개키) 방식이다.&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;b&gt;대칭키&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대칭키는 메시지를 암호화하고 암호를 해독하는 &lt;u&gt;&lt;b&gt;키가 단 하나밖에 없는 암호화&lt;/b&gt;&lt;/u&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;u&gt;&lt;b&gt;무조건 이 비밀키(대칭키)를 비밀로 유지해야 된다는 점이다.&lt;/b&gt;&lt;/u&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;(뛿쀍뛹띍fjlsd_%#%#% )&lt;/p&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;u&gt;&lt;b&gt;난독&lt;/b&gt;을 유지&lt;/u&gt;할 수 있고 해당 과정을 거꾸로 수행하는 과정을 통해 &lt;b&gt;해독&lt;/b&gt;을 진행할 수 있다. 이런 암호화키는 메시지와 함께 혼합되어 있어 알고리즘을 알고있어도 &lt;u&gt;키가 없다면 메시지의 내용은 전혀 알 수 없다.&lt;/u&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;u&gt;키를 &lt;b&gt;공유&lt;/b&gt;하는 것이 매우 어렵다는 것이다&lt;/u&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;b&gt;비대칭키(공개키, 비공개키)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나는 &lt;u&gt;공개키&lt;/u&gt;이고 다른 하나는 &lt;u&gt;비공개키&lt;/u&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;공개키를 모두에게 공개하여 쉽게 암호화할 수 있게하고 비공개키는 나만 갖고있어 누구나 내게 메시지를 보낼 수 있지만 그 메시지를 읽을 수 있는 사람은 비공개키를 갖고 있는 나밖에 없다는 것이다. 이 방법은 2개의 키 소유자만 메시지를 열어볼 수 있기 때문에 개인정보보호 뿐만아니라 식별에도 유용하다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;&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;이제 이러한 두가지 키 암호화 방식을 토대로 어떻게 SSL 인증처리를 진행하는지 살펴보자.&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;b&gt;핸드쉐이크(handshake)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 유명한 A 포털사이트에 접근하게 된다면 브라우저 URL 창에 자물쇠를 보게 될텐데 어떻게 처리된 걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 A 포털사이트를 보는 시점에서는 브라우저와 A 포털사이트 서버간의 보안 연결 설정을 이미 마친 상태이다. 그러나 이전에 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 브라우저와 네이버 서버간의 협상 프로세스를 핸드쉐이크라고 한다.&lt;span&gt;&amp;nbsp;&lt;/span&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;b&gt;클라이언트 헬로&lt;/b&gt; : 나의 브라우저는 SSL/TLS 버전 및 암호화 알고리즘을 A 포털사이트 서버쪽으로 전달한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 헬로&lt;/b&gt; : A 포털사이트 서버는 나의 브라우저가 보낸 SSL/TLS 버전과 암호화 알고리즘을 선택한다. 그리고 A 포털사이트 서버의 공개키가 포함된 SSL인증서로 회신하여 식별할 수 있도록 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 키 교환&lt;/b&gt; : A 포털사이트 서버에서 보낸 인증서가 합법적인지 확인이 필요하다. (브라우저 CA 목록 참조) 그 후, pre-master-key를 생성하여 제공받은 A 포털사이트 서버의 인증서에 담겨진 공개키로 암호화하여 A 포털사이트 서버로 전달한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Chipher 스펙 변경&lt;/b&gt; : A 포털사이트 서버는 개인키를 사용하여 암호화된 pre-master-key를 해독한다. 이제 각각의 과정을 통해 master-key를 만들어내고 이후 해당 키를 토대로 세션을 유지한다.&lt;/li&gt;
&lt;/ol&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;u&gt;비대칭키 방식으로 대칭키를 &lt;b&gt;공유&lt;/b&gt;&lt;/u&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;&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;https://howhttps.works/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://howhttps.works/ko/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664949719602&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;How HTTPS works&quot; data-og-description=&quot;  A cat explains how HTTPS works...in a comic!  &quot; data-og-host=&quot;howhttps.works&quot; data-og-source-url=&quot;https://howhttps.works/ko/&quot; data-og-url=&quot;https://howhttps.works&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bi3hm7/hyP2elY4rL/6k9ZO8suNWaFRHoLQrp0i0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://howhttps.works/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://howhttps.works/ko/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bi3hm7/hyP2elY4rL/6k9ZO8suNWaFRHoLQrp0i0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;How HTTPS works&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  A cat explains how HTTPS works...in a comic!  &lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;howhttps.works&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>흑구의 공부내용 공유</category>
      <category>HTTP Secure</category>
      <category>https</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/482</guid>
      <comments>https://sas-study.tistory.com/482#entry482comment</comments>
      <pubDate>Wed, 5 Oct 2022 15:47:08 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Java 14, Record 키워드</title>
      <link>https://sas-study.tistory.com/481</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;객체간에 변경할 수 없는(immutable) 데이터를 전달하는 것은 가장 흔하게 웹 어플리케이션을 개발하면서 만날 수 있는 작업입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 14 이전에는 &lt;u&gt;&lt;b&gt;boilerplate code&lt;/b&gt;&lt;/u&gt; 가 포함된 클래스를 만들어야 했으며 이는 사소한 실수와 복잡한 의도 등에 취약했습니다. Java14가 출시되면서 이제 record 키워드를 사용하여 &lt;u&gt;이런 &lt;b&gt;문제&lt;/b&gt;들을&lt;/u&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;b&gt;Record&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;record 키워드 사용하기 이전&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 많은 상황에서 데이터를 전달하는 클래스는 데이터베이스에서 데이터를 꺼내와서 http 통신 너머의 유저에게 데이터를 전달하여 보여주는 역할을 하게 됩니다. 대부분 말씀하시는 DTO가 이런 역할로 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 경우 이러한 데이터를 변경하여 전달하기보다 해당 디비 raw 데이터를 가공하여 다른 컬럼으로 값을 집어넣고 또 다른 DTO에 래핑하여 전달하거나 각 Layer마다 컨버팅 작업을 거치게 됩니다.&lt;/p&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;private final 필드 추가.&lt;/li&gt;
&lt;li&gt;각 필드에 대한 getter 접근 메소드&lt;/li&gt;
&lt;li&gt;각 필드에 해당하는 인자가 있는 public 생성자&lt;/li&gt;
&lt;li&gt;모든 필드가 일치할 때 동일한 클래스의 객체에 대해 true를 반환하는 equals 메소드&lt;/li&gt;
&lt;li&gt;모든 필드가 일치할 때 동일한 값을 반환하는 hashCode 메소드&lt;/li&gt;
&lt;li&gt;클래스의 이름과 각 필드의 이름과 해당 값을 포함하는 toString 메소드&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 아래의 Person 클래스를 보시죠.&lt;/p&gt;
&lt;pre id=&quot;code_1664872309289&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Person {

    private final String name;
    private final String address;

    // 혹은 빌더 사용.
    public Person(String name, String address) {
        this.name = name;
        this.address = address;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, address);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (!(obj instanceof Person)) {
            return false;
        } else {
            Person other = (Person) obj;
            return Objects.equals(name, other.name)
              &amp;amp;&amp;amp; Objects.equals(address, other.address);
        }
    }

    @Override
    public String toString() {
        return &quot;Person [name=&quot; + name + &quot;, address=&quot; + address + &quot;]&quot;;
    }

    // 이후로 getter 클래스.
}&lt;/code&gt;&lt;/pre&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;많은 boilerplate code&lt;/li&gt;
&lt;li&gt;클래스의 목적의 모호함(너무 많은 메소드 내용 때문에 클래스의 목적이 정보전달인지 알기 어려움)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 클래스를 설계할때마다 너무 많은 메소드들을 작성해야 합니다. 근데 그 작성이 마치 받아쓰기만큼 &lt;u&gt;&lt;b&gt;반복적&lt;/b&gt;&lt;/u&gt;이라는 것이 문제입니다. Person이 아닌 다른 클래스 수백개를 설계할 때도 동일한 작업을 해야할텐데.. 매우 귀찮은 일이 아닐 수 없습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텔리제이 IDE 를 활용하게 된다면 별도로 구현을 작성해주는 기능이 있겠지만 어디까지나 현재 기준으로만 작성되어 &lt;u&gt;추가 필드가 생기게 된다면 이를 다시 작성&lt;/u&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;그나마 Lombok이라는 라이브러리를 활용하여 이러한 boilerplate code를 줄일 수 있지만 여전히 클래스 자체는 정보를 담는 용도인지 다른 용도가 있는지 코드의 흐름을 보지 않는 이상 키워드로써 알기는 어렵습니다.(단순히 class 선언만 되어있기 때문에)&lt;/p&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;Lombok&lt;/b&gt; 사용 예)&lt;/p&gt;
&lt;pre id=&quot;code_1664946006941&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EqualsAndHashCode
@ToString
@AllArgsConstructor
@Getter
public class Person {
    private final String name;
    private final String address;
}&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;또한 이러한 boilerplate code의 노출때문에 실제 이 클래스의 목적이 정보전달인지 비즈니스 로직이 포함된 메소드가 있는 클래스인지 매우 모호합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;record 키워드 사용하기 이후&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 14 이후로는 반복적인 데이터 클래스를 record 키워드를 사용하여 대체할 수 있습니다. record는 immutable 한 데이터 클래스이며 필드의 데이터 타입과 이름만 필요로 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;public record Person (String name, String address) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record 를 선언하는 방법은 다음과 같습니다. class 키워드 대신 record 키워드로 변경되었습니다. 클래스명 옆에 마치 코틀린 생성자처럼 필드를 선언해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;equals, hashCode, toString, public 생성자를 생성해주면서, 변수들은 private 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;&lt;b&gt;1) 생성자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record를 사용하면 각 필드에 대한 인수가 생성된 public 생성자가 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Person 클래스의 경우 생성자는 아래와 선언된 것과 똑같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664946646575&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Person(String name, String address) {
    this.name = name;
    this.address = address;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 방법도 같습니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    Person person = new Person(&quot;흑구&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;&lt;b&gt;2) getter&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 getter 메소드는 getXXX 처럼 get이라는 prefix를 포함하여 호출했어야 하지만 record는 &lt;u&gt;필드명과 동일한 메소드명으로 호출&lt;/u&gt;할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    Person person = new Person(&quot;흑구&quot;, &quot;흑구 주소&quot;);

    System.out.println(person.name());
    System.out.println(person.address());
    
    // 흑구
    // 흑구 주소
}&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;b&gt;3) equals&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 equals 메소드가 생성됩니다. 인자로 넘어온 객체가 동일한 객체이고 모든 해당 필드의 값이 동일하다면 true를 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    String name = &quot;흑구&quot;;
    String address = &quot;흑구 주소&quot;;
    Person person1 = new Person(name, address);
    Person person2 = new Person(name, address);

    System.out.println(person1.equals(person2));
    System.out.println(Objects.equals(person1, person2));
    
    // true
    // true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자체 equals와 Objects 유틸 클래스의 equals 메소드가 동일하게 동작한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    String name = &quot;흑구&quot;;
    String address = &quot;흑구 주소&quot;;
    Person person1 = new Person(name, address);
    Person person2 = new Person(name, address + &quot;2&quot;);

    System.out.println(person1.equals(person2));
    System.out.println(Objects.equals(person1, person2));

    // false
    // false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 값을 가진다면 false를 리턴한다.&lt;/p&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;b&gt;4) hashCode&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;equals 메소드와 유사하게 해당 hashCode 메소드도 생성됩니다. hashCode 메소드는 두 객체의 모든 필드값이 일치하는 경우 두 Person 객체에 대해 동일한 값을 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    String name = &quot;흑구&quot;;
    String address = &quot;흑구 주소&quot;;
    Person person1 = new Person(name, address);
    Person person2 = new Person(name, address);

    System.out.println(person1.hashCode());
    System.out.println(person2.hashCode());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드값중 하나라도 다르다면 hashCode 값이 달라집니다.&lt;/p&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;5) toString&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record의 이름을 포함하는 문자열을 생성하는 toString 메소드 입니다. 결과는 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    String name = &quot;흑구&quot;;
    String address = &quot;흑구 주소&quot;;
    Person person = new Person(name, address);

    System.out.println(person);
    System.out.println(person.toString());

    // Person[name=흑구, address=흑구 주소]
    // Person[name=흑구, address=흑구 주소]
}&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;record 생성자&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 인수를 인자를 받는 public 생성자가 동작하는 동안 생성자 실행에 대한 커스터마이징을 할 수 있습니다. 이러한 커스터마이징은 주로 유효성 검사를 하기 위한 작업으로 최대한 심플하게 넘겨야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 아래처럼 Person 레코드에 제공된 name과 address가 null값이 아닌지 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664947747153&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public record Person(String name, String address) {
    public Person {
        Objects.requireNonNull(name);
        Objects.requireNonNull(address);
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자 목록을 제공하여 새로운 생성자를 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public record Person(String name, String address) {
    public Person {
        Objects.requireNonNull(name);
        Objects.requireNonNull(address);
    }

    public Person(String name) {
        this(name, &quot;Unknown&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;클래스 생성자와 마찬가지로 필드는 this 키워드를 사용하여 참조할 수 있으며 파라미터는 필드이름과 일치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 public 생성자와 동일한 파라미터를 선언하여 생성자를 생성할 수는 있지만 각 필드를 수동으로 처리해줘야합니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public record Person(String name, String address) {
    public Person(String name, String address) {
        this.name = name;
        this.address = address;
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 파라미터가 없는 생성자를 선언하고 기본 public 생성자와 일치하는 파라미터 목록이 있는 생성자를 선언하면 컴파일 에러가 발생합니다. 따라서 아래의 코드는 컴파일이 되지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public record Person(String name, String address) {
    public Person {
        Objects.requireNonNull(name);
        Objects.requireNonNull(address);
    }

    public Person(String name, String address) {
        this.name = name;
        this.address = address;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정적 변수 및 메소드(static 필드)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 Java 클래스와 마찬가지로 record 클래스에 &lt;b&gt;정적(static) 변수와 메소드&lt;/b&gt;를 작성할 수 있습니다. 사용하는 방식은 일반 클래스와 크게 상이하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public record Person(String name, String address) {
    public static String UNKNOWN_NAME = &quot;UNKNOWN_NAME&quot;;
    public static String UNKNOWN_ADDRESS = &quot;UNKNOWN_ADDRESS&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 클래스와 동일한 구문을 사용하여 &lt;u&gt;정적 메소드&lt;/u&gt;를 선언할 수 있습니다. 아래의 메소드는 정적 메소드로 인스턴스를 리턴합니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public record Person(String name, String address) {
    public static String UNKNOWN_NAME = &quot;UNKNOWN_NAME&quot;;
    public static String UNKNOWN_ADDRESS = &quot;UNKNOWN_ADDRESS&quot;;
    
    public static Person unnamed(String address) {
        return new Person(UNKNOWN_NAME, address);
    }
    
    public static Person unAddress(String name) {
        return new Person(name, UNKNOWN_ADDRESS);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record 이름을 활용하여 정적 변수와 정적 메소드 모두를 참조할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    String name = &quot;흑구&quot;;
    String address = &quot;흑구 주소&quot;;
    Person unNamed = Person.unnamed(address);
    Person unAddress = Person.unAddress(name);

    System.out.println(Person.UNKNOWN_NAME);
    System.out.println(Person.UNKNOWN_ADDRESS);
    System.out.println(unNamed);
    System.out.println(unAddress);
    
    //UNKNOWN_NAME
    //UNKNOWN_ADDRESS
    //Person[name=UNKNOWN_NAME, address=흑구 주소]
    //Person[name=흑구, address=UNKNOWN_ADDRESS]
}&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;필자 생각.&lt;/b&gt;&lt;/u&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;해당 기능이 왜 생성되었을까를 생각해보다가.. DTO 와 VO라는 개념에 대해서 생각해보기도 하고 코틀린의 data 클래스에 대해서 생각해보기도 하니 나름대로 결론을 내었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서도 data 클래스를 생성한 이유가 별도의 목적성을 표기하기 위함이었으므로 자바에서도 이러한 클래스 사용 목적에 대한 표기를 언어적 차원에서 제공했으면 하는 수요가 있지 않았을까 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논리적인 개념으로만 DTO, VO 이런식으로 데이터 클래스의 성질을 나뉘었을 뿐 물리적 언어적 차원에서 나뉜것은 아니기 때문에 다른 개발자에 의해,, 코드 수정에 의해 충분히 목적이 변질될 수 있고, 나 또한 그런상황에서 편리함에 취해 마구마구 변경했던 과거들이 있었기 때문에 해당 문법의 등장 배경을 생각해보지 않을 수 없었다.&lt;/p&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>프로그래밍 언어/Java</category>
      <category>java 14 record</category>
      <category>java 14 record keyword</category>
      <category>자바 record</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/481</guid>
      <comments>https://sas-study.tistory.com/481#entry481comment</comments>
      <pubDate>Wed, 5 Oct 2022 14:45:48 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] Kotlin sealed 클래스(실드 클래스)</title>
      <link>https://sas-study.tistory.com/480</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실드 클래스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Parent 클래스가 있을 때, 이를 상속하는(인터페이스라면 구현하는) 다른 Child1, Child2, ... 클래스가 있다고 가정하면, 컴파일러는 현재 시점에서 어떤 클래스가 Parent를 상속하고 상속하지 않는지 알수가 없다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;abstract class AbstractRoom {
    abstract val roomType: String
    abstract val title: String
    abstract fun tellStatus()
}

data class RoomApt(override val title: String) : AbstractRoom() {
    override val roomType: String = &quot;아파트&quot;;
    override fun tellStatus() {
        println(&quot;$roomType 매물은 현재 계약 가능합니다.&quot;)
    }
}

data class RoomOfficetel(override val title: String) : AbstractRoom() {
    override val roomType: String = &quot;오피스텔&quot;;
    override fun tellStatus() {
        println(&quot;$roomType 매물은 현재 계약 가능합니다.&quot;)
    }
}

object Dabang {
    val map = mutableMapOf&amp;lt;String, AbstractRoom&amp;gt;()

    fun getRoom(roomType: String) = map[roomType]
    fun addRoom(room: AbstractRoom) {
        when(room) {
            is RoomApt -&amp;gt; map[room.roomType] = room
            is RoomOfficetel -&amp;gt; map[room.roomType] = room
            else -&amp;gt; {
                println(&quot;그런 매물 없습니다.&quot;)
            }
        }
    }
}

fun main() {

    val roomApt = RoomApt(title = &quot;아파트 매물입니다.&quot;)
    Dabang.addRoom(roomApt)
    val roomOfficetel = RoomOfficetel(title = &quot;오피스텔 매물입니다.&quot;)
    Dabang.addRoom(roomOfficetel)

    println(Dabang.getRoom(&quot;아파트&quot;))
    println(Dabang.getRoom(&quot;오피스텔&quot;))

}
//RoomApt(title=아파트 매물입니다.)
//RoomOfficetel(title=오피스텔 매물입니다.)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- AbstractRoom : Parent 클래스를 담당하는 추상 클래스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- RoomApt, RoomOfficetel : AbstractRoom 클래스를 상속하는 구현 클래스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Dabang: 두 매물을 담아 광고하는 일종의 저장소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 해당 코드의 main 실행결과는 무난하게 출력된다.&lt;/p&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 class=&quot;kotlin&quot;&gt;&lt;code&gt;object Dabang {
    val map = mutableMapOf&amp;lt;String, AbstractRoom&amp;gt;()

    fun getRoom(roomType: String) = map[roomType]
    fun addRoom(room: AbstractRoom) = when (room) {
        is RoomApt -&amp;gt; map[room.roomType] = room
        is RoomOfficetel -&amp;gt; map[room.roomType] = room
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 이런식으로 when 표현식으로 변경 후에 함수의 결과로써 행해져야 한다면.. 이야기가 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 컴파일이 되지 않는다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Kotlin:&amp;nbsp;'when'&amp;nbsp;expression&amp;nbsp;must&amp;nbsp;be&amp;nbsp;exhaustive,&amp;nbsp;add&amp;nbsp;necessary&amp;nbsp;'else'&amp;nbsp;branch&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;else 구문을 사용하라고 한다. 가만보니 when 절에 RoomApt, RoomOfficetel 타입 말고는 따로 지정해서 정해준적이 없다. 그러니 함수의 인자로 다른 타입의 인스턴스가 넘어오는 경우 제대로된 처리를 할 수 없게되니 else로 고려하라는 말인 것 같다.&lt;/p&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 class=&quot;kotlin&quot;&gt;&lt;code&gt;sealed class AbstractRoom {
    abstract val roomType: String
    abstract val title: String
    abstract fun tellStatus()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;abstract&amp;nbsp; 키워드를 sealed로 수정했다.&lt;/p&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;-&amp;gt; 매우 잘된다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;else 절만 잘 써놓으면 이런 불필요한 컴파일 에러는 잘 넘길 수 있는게 아닌가??&lt;/p&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;그 많은 코드를 스스로 짜더라도 자신의 코드를 기억하지 못하는 개발자가 더 많을텐데 else 절로 저렇게 퉁쳐버리면 어떤 코드에서 어떤 불필요한 버그가 발생할지 &lt;u&gt;런타임이 아닌 이상 확인하기 어렵다.&lt;/u&gt;&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;런타임이 아닌 이상 확인하기 어렵다는 말은 달리 말하면 실행해보고 하지 않으면 모를 수 있다는 뜻이다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이렇게 런타임에서 발생할 수 있을 법한 이슈를 sealed 라는 키워드로 제한해놓고 컴파일단에서 확인할 수 있게 해준다면 코틀린 언어라는 자체에서 개발자로 하여금 얻을 수 있게 해주는 방어요소가 너무나도 많은 것 같다.(위에서 언급한 런타임 에러)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;싱글톤 선언 키워드도 그랬고 이러한 sealed 클래스도 그러한 부분이 아닌가 싶다. 물론 sealed 키워드는 어떤 디자인 패턴적인 요소는 아니지만 자바에서 switch문을 썻을 때 반드시 default를 걸어놓아서 예외처리를 한다던가 등등의 개발 패턴에서의 단점을 kotlin에서 해결하고자 한것으로 판단되니 말이다.(필자의 개인적 생각입니다.)&lt;/p&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>프로그래밍 언어/Kotlin</category>
      <category>kotlin sealed</category>
      <category>개발 언어 공부</category>
      <category>코틀린 sealed</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/480</guid>
      <comments>https://sas-study.tistory.com/480#entry480comment</comments>
      <pubDate>Wed, 5 Oct 2022 07:12:12 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] 코틀린 싱글턴, 동반객체 (object, companion)</title>
      <link>https://sas-study.tistory.com/479</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;싱글톤이란&lt;/b&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;&lt;a href=&quot;https://sas-study.tistory.com/478&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sas-study.tistory.com/478&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664917965347&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;[Java Design Pattern] 자바 디자인패턴, Singleton 싱글톤 패턴&quot; data-og-description=&quot;싱글톤 패턴 시스템 런타임이나 환경 셋팅에 대한 정보등 클래스 인스턴스가 여러개일 때 문제가 발생할 수 있는 요구사항이 있다. 이럴 경우 싱글톤 패턴을 활용한다면 인스턴스를 오직 한개&quot; data-og-host=&quot;sas-study.tistory.com&quot; data-og-source-url=&quot;https://sas-study.tistory.com/478&quot; data-og-url=&quot;https://sas-study.tistory.com/478&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/MIOf8/hyP0zZjwbs/QNIyNU6deFNKab4etv13bK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bWOWuK/hyP0DtQWp8/VzXeHI00JUO3M2T7KfpDT0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bwlOHa/hyP19Y0tQg/X2OxC48j9r9ANW59jkiEj0/img.jpg?width=910&amp;amp;height=512&amp;amp;face=0_0_910_512&quot;&gt;&lt;a href=&quot;https://sas-study.tistory.com/478&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sas-study.tistory.com/478&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/MIOf8/hyP0zZjwbs/QNIyNU6deFNKab4etv13bK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bWOWuK/hyP0DtQWp8/VzXeHI00JUO3M2T7KfpDT0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bwlOHa/hyP19Y0tQg/X2OxC48j9r9ANW59jkiEj0/img.jpg?width=910&amp;amp;height=512&amp;amp;face=0_0_910_512');&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;[Java Design Pattern] 자바 디자인패턴, Singleton 싱글톤 패턴&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;sas-study.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;코틀린에서의 싱글톤&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바처럼 직접 클래스로 구현하는 형태가 아닌 문법적으로 지원해준다. object라는 객체 선언 키워드를 통해 싱글톤을 지원합니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;object Singleton {
}
&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;싱글톤 클래스의 함수나 변수를 사용할 때는 자바의 정적 static에 접근하듯이 클래스 한정자를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;object Singleton {
    val a = 1234

    fun printA() = println(a)
}

fun main() {

    println(Singleton.a)
    Singleton.printA()

// 1234
// 1234
}&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;이러한 객체 선언을 통해 좀더 응용해보자면, static 키워드를 이용하여 XXUtil 클래스를 만들고 사용해본적이 있을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제) LocalDateTimeUtil&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

object LocalDatetimeUtils {

    val now: LocalDateTime
        get() = LocalDateTime.now()

    const val DEFAULT_FORMAT = &quot;yyyy-MM-dd&quot;

    fun same(a: LocalDateTime, b: LocalDateTime) :Boolean {
        return a == b
    }
    fun getNowString(): String {
        return now.format(DateTimeFormatter.ofPattern(DEFAULT_FORMAT))
    }
}

fun main() {
    println(LocalDatetimeUtils.now)
    println(LocalDatetimeUtils.now)
    println(LocalDatetimeUtils.now)

    println(LocalDatetimeUtils.DEFAULT_FORMAT) // yyyy-MM-dd

    val now = LocalDateTime.now()
    println(LocalDatetimeUtils.same(now, now)) // true

    val nowString = LocalDatetimeUtils.getNowString()
    println(nowString)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린을 문법을 학습하지 않고 자바에서 싱글톤을 구현하듯이 코틀린에서 클래스를 설계하게 되면 static inner 클래스에서 막히게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자도 어?? 왜 inner 클래스 선언이 안돼지?? 하고 당황했던 기억이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 별도의 inner 클래스 키워드인 &lt;b&gt;companion object&lt;/b&gt;&amp;nbsp;가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1664918673493&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyClass {

	companion object {
		
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 한 클래스 내에서 한개의 companion object를 선언할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- companion object는 익명 클래스처럼 동작하지만 스스로 이름을 가지게 할 수 있다.&lt;/p&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;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class MyClass {

    private constructor()

    companion object CompanionObjectName {
        val a = 1234

        fun newInstance() = MyClass()
    }
}

fun main() {
    println(MyClass.a)
    println(MyClass.newInstance())

    // 이렇게도 가능하나 생략 가능
    println(MyClass.Companion.a)
    println(MyClass.Companion.newInstance())
    
    // 이름이 바뀌게 된다면 -&amp;gt; CompanionObjectName
    println(MyClass.CompanionObjectName.a)
    println(MyClass.CompanionObjectName.newInstance())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여기서 MyClass는 일종의 간단한 싱글턴으로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- companion 동반객체는 클래스당 하나만 존재하므로 사실 부모 클래스로부터의 공간이 이미 확보된 상태이기 때문에 접근하는 방법에 자바의 static 접근 방법과 동일하게 접근할수 있다. 하지만 별도로 이름을 통해 명시해서 접근도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이름이 없다면 Companion이라는 키워드로, 이름이 있다면 별도로 이름을 통해 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/479</guid>
      <comments>https://sas-study.tistory.com/479#entry479comment</comments>
      <pubDate>Wed, 5 Oct 2022 06:29:55 +0900</pubDate>
    </item>
    <item>
      <title>[Java Design Pattern] 자바 디자인패턴, Singleton 싱글톤 패턴</title>
      <link>https://sas-study.tistory.com/478</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;싱글톤 패턴&lt;/b&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;pre id=&quot;code_1664593968006&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Settings {
    private static Settingsinstance;

    private Settings() { }

    public static Settings getInstance() {
        if (instance== null) {
            instance= new Settings();
        }
        return instance;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤 구현 방법중에서 이러한 예제를 가장 많이 보았을 것 같다. 하지만 이 예제는 &lt;u&gt;&lt;b&gt;멀티스레드&lt;/b&gt;&lt;/u&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_1664594073691&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Setting {
    private static Setting instance;

    private Setting() { }

    public static Setting getInstance() throws Exception {
        if (instance== null) {
            Thread.sleep(1000); // 1초 동안 무언가 엄청 많은 작업을 한다.
            instance= new Setting();
        }
        return instance;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우가 됐을때, 멀티스레드 환경에서 해당 코드를 호출하면 어떻게 될까? 2개의 스레드가 if문을 모두 통과하여 결과적으로 마지막으로 수행된 new 연산자에 의해 instance는 변경이 될것이고 결과적으로 heap에는 2개의 인스턴스가 쌓였을 것이다.(결국 하나는 garbage가 되겠지만..)&lt;/p&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;u&gt;멀티스레드 환경에서의 문제점을 초점으로 개선&lt;/u&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;u&gt;동기화&lt;/u&gt;&lt;/b&gt;를 해주면 어떨까?&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class Setting {
    private static Setting instance;

    private Setting() { }

    public static synchronized Setting getInstance() throws Exception {
        if (instance== null) {
            Thread.sleep(1000); // 1초 동안 무언가 엄청 많은 작업을 한다.
            instance= new Setting();
        }
        return instance;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 메소드에 synchronized 키워드를 붙여 스레드가 해당 메소드에 접근할때마다 메소드 접근을 순차적으로 접근하도록 하면 되지 않을까?&lt;/p&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;u&gt;동기화 작업에 대한 부담&lt;/u&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;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Setting {
    private static Setting instance = new Setting();

    private Setting() { }

    public static Setting getInstance(){
        return instance;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 클래스의 인스턴스에 접근할때가 아니라 개발자가 접근하기 이전부터 가지고 있으면 되지 않을까?(이른 초기화, Eager Initialization)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 가벼운 실무에서는 이러한 내용이 맞고 적절히 넘어갈 수도 있다고 본다. 하지만 private 생성자 안쪽 코드를 비워놔서 괜찮지 인스턴스를 미리 생성해둔다는 것은 사용하지 않게되면 불필요한 메모리가 heap에 쌓여 있다고 볼 수도 있다.&lt;/p&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;u&gt;정확한 그 타이밍에 동기화가 적용되어 순차성을 보장&lt;/u&gt;해야만하며 싱글톤 인스턴스가 생성된 이후로는 &lt;u&gt;동기화 작업은 진행하지 않고&lt;/u&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 class=&quot;routeros&quot;&gt;&lt;code&gt;public class Setting {
    private static volatile Setting instance;

    public static Setting getInstance() {
        if (instance== null) {
            synchronized (Setting.class) {
                if (instance== null) {
                    instance= new Setting();
                }
            }
        }
        return instance;
    }
}&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;해당 코드를 보면 동기화는 Setting 클래스에 해당하는 타입에 대해서만 진행하며 두번의 instance에 대한 null 체크가 존재한다. 이를 double checked locking이라고 하는데, 첫번째 if문은 빠르게 통과할 것이고, 동기화 블럭은 싱글턴 인스턴스를 생성해주는 블럭이기 때문에 동기화 순차성에 의해 1번만 호출될 것이다. 그러기 위해서 두번째 if문이 있다. 해당 코드가 자연스럽게 instance 변수를 통해서 리턴될 수 있도록 디자인되어있다.&lt;/p&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 class=&quot;cpp&quot;&gt;&lt;code&gt;volatile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 키워드는 java 1.5부터 동작하게 될텐데 이에 대한 내용은 &lt;a href=&quot;https://steady-coding.tistory.com/555&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;그런데 말입니다.&lt;/p&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;pre class=&quot;java&quot;&gt;&lt;code&gt;public class Setting {

    private Setting() { }

    private static class SettingHolder {
        private static final Setting INSTANCE = new Setting();
    }

    public static Setting getInstance() {
        return SettingHolder.INSTANCE;
    }
}&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;- static inner class를 활용하여 해당 SettingHoler 클래스에 접근하지 않는 경우 INSTANCE 변수 및 클래스 정보는 아직 초기화되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 개발자가 getInstance()를 최초 호출하는 경우 SettingHolder 클래스가 로드되며 그 안쪽의 INSTANCE 변수 또한 초기화되어 싱글톤 인스턴스가 채워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이후로 호출하는 경우는 INSTANCE 변수에 접근하는 것일 뿐 new Setting() 코드는 더이상 동작하지 않기 때문에 싱글턴을 유지할 수 있습니다.&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;b&gt;리플렉션 이슈&lt;/b&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;아래의 코드를 보자&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class Main {
    public static void main(String[] args) throws Exception {
        Constructor&amp;lt;Setting&amp;gt; declaredConstructor = Setting.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Setting setting1 = declaredConstructor.newInstance();
        Setting setting2 = Setting.getInstance();
        Setting setting3 = Setting.getInstance();
    
        System.out.println(setting1 == setting2); // false
        System.out.println(setting2 == setting3); // true
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 리플렉션을 활용하여 Setting 클래스의 생성자에 접근하고 private으로 선언된 생성자의 접근 가능성을 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;이처럼 자바에서는 리플렉션이라는 강력한 클래스 정보 접근 기술이 있기 때문에 해당 기술을 활용하지 않는 한 싱글톤 패턴은 유지될 수 있다. 사실 이렇게 복잡한 방법으로 인스턴스를 생성하는 개발방식은 거의 모든 실무에서 사용하지 않겠지만 결과적으로 방법은 열려 있다는 것은 변하지 않는다.&lt;/p&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;정답은 enum을 활용하면 된다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum Setting {
    INSTANCE;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 Enum으로 쓰게되면 리플렉션 기술을 이용하더라도 생성자는 호출할 수 없다. 그런데 enum은 말그대로 상수이기 때문에 사용하기 조금 난해한 경우가 있을 것 같았다. -&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;b&gt;static inner class&lt;/b&gt;를 활용하지 않을까 싶다.&amp;nbsp;&lt;/p&gt;</description>
      <category>자바 디자인패턴</category>
      <category>Singleton Design Pattern</category>
      <category>디자인 패턴 싱글톤</category>
      <category>싱글톤 static inner class</category>
      <category>싱글톤 synchronized</category>
      <category>싱글톤 volatile</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/478</guid>
      <comments>https://sas-study.tistory.com/478#entry478comment</comments>
      <pubDate>Sat, 1 Oct 2022 12:47:29 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] kotlin data class</title>
      <link>https://sas-study.tistory.com/477</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;데이터 클래스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 클래스는 &lt;u&gt;데이터를 &lt;b&gt;보관&lt;/b&gt;하거나 &lt;b&gt;전달&lt;/b&gt;하기 위한 목적&lt;/u&gt;을 가진 객체를 설계할 때 활용할 수 있습니다.(DTO)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class 키워드 앞에 data 를 선언하여 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;data class Room(var roomType: String, var price: Int)&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;이렇게 data 키워드를 사용하게 되면 코틀린 컴파일러가 아래의 함수들을 자동으로 생성해줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;equals()&lt;/li&gt;
&lt;li&gt;hashCode()&lt;/li&gt;
&lt;li&gt;toString()&lt;/li&gt;
&lt;li&gt;componentN()&lt;/li&gt;
&lt;li&gt;copy()&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 자바 개발자분들은 Spring Framework를 이용하여 웹 어플리케이션을 개발할 때, &lt;u&gt;Lombok&lt;/u&gt; 라이브러리를 많이 이용하셨을텐데요. 거기에서 &lt;b&gt;@Data&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;뿐만 아니라 JDK 15이상을 사용하시는 자바 개발자분들은 &lt;b&gt;record&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;이렇게 클래스를 data class로 선언하게 된다면 별도의 구현없이 위의 5가지 함수들을 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Why Data Class&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 클래스를 사용하는 이유는 equals, hashCode, toString을 통해 정보전달 혹은 저장을 위한 개발에 많은 기능적, 개념적 도움을 주기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 클래스에서는 위 세가지 함수들을 직접 구현하거나 IDE를 통해 생성해야하는 불편함이 있는데, 데이터 클래스는 이에 대해서 판을 제대로 깔아줬다는 느낌이 들었습니다.&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;객체 동등성 비교 이슈(equals)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 객체 동등성 비교를 하기 위해 equals 메소드를 재정의하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터 클래스와 일반 클래스의 차이점을 확인해보자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Room(var roomType: String, var price: Int)
data class RoomData(var roomType: String, var price: Int)
fun main() {

    var room1: Room = Room(&quot;원룸&quot;, 300_000)
    var room2: Room = Room(&quot;원룸&quot;, 300_000)
    println(room1 == room2) // false

    var roomData1: RoomData = RoomData(&quot;원룸&quot;, 300_000)
    var roomData2: RoomData = RoomData(&quot;원룸&quot;, 300_000)
    println(roomData1 == roomData2) // true
}&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;u&gt;첫번째&lt;/u&gt; 비교는 일반 클래스이기 때문에 equals 메소드가 재정의되어 있지 않아 Object 클래스의 equals 메소드를 사용하게 되어 서로 다른 인스턴스 이기 때문에 false를 리턴하게 됩니다.&lt;/p&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;u&gt;두번째&lt;/u&gt; 비교는 데이터 클래스이기 때문에 equals 메소드가 각 변수별로 선언되어 있어 모든 클래스 변수가 같은 값을 가지면 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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해시 코드 이슈(hashCode)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- equals 함수를 재정의할 때 반드시 재정의 해주어야하는 파트너 함수.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JVM을 런타임 플랫폼으로 사용하는 언어라면 반드시 &lt;u&gt;equals가 true를 리턴했다면 hashCode 또한 true&lt;/u&gt;를 리턴해야 한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {

    var room1: Room = Room(&quot;원룸&quot;, 300_000)
    var room2: Room = Room(&quot;원룸&quot;, 300_000)
    println(room1 == room2) // true -&amp;gt; equals를 구현했기 때문

    val map = hashMapOf&amp;lt;Room, Int&amp;gt;()
    map[room1] = 1
    val result = map.containsKey(room2)
    println(result) // false -&amp;gt; hashCode를 구현하지 않았기 때문.
}&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;equals, hashCode가 모두 구현되었다면..&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class Room(var roomType: String, var price: Int) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Room

        if (roomType != other.roomType) return false
        if (price != other.price) return false

        return true
    }

    override fun hashCode(): Int {
        var result = roomType.hashCode()
        result = 31 * result + price
        return result
    }
}
data class RoomData(var roomType: String, var price: Int)
fun main() {

    var room1: Room = Room(&quot;원룸&quot;, 300_000)
    var room2: Room = Room(&quot;원룸&quot;, 300_000)
    println(room1 == room2) // true -&amp;gt; equals를 구현했기 때문

    val map = hashMapOf&amp;lt;Room, Int&amp;gt;()
    map[room1] = 1
    val result = map.containsKey(room2)
    println(result) // true -&amp;gt; hashCode를 구현했기 때문.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 Hash가 들어가는 자료구조의 경우에만 이러한 이슈가 발생하기 때문에 내부적으로 Hash를 사용하는 컬렉션의 자료구조를 들여다 봐야합니다. hashmap 혹은 hashset의 경우에는 내부적으로 key를 탐색하기 위한 방법으로써 equals와 hashCode가 모두 사용되고 있습니다. 결과적으로 해당 hash 코드를 활용하는 컬렉션 구현체들은 equals 가 true 일 때, hashCode 또한 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;하지만 data 클래스를 사용하게 된다면 내부적으로 컴파일러가 해당 함수들을 모두 내장시켜주기 때문에 해당 이슈에 대해서는 걱정할 필요가 덜합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;data class RoomData(var roomType: String, var price: Int)
fun main() {
    var roomData1: RoomData = RoomData(&quot;원룸&quot;, 300_000)
    var roomData2: RoomData = RoomData(&quot;원룸&quot;, 300_000)
    println(roomData1 == roomData2) // true

    val map = hashMapOf&amp;lt;RoomData, Int&amp;gt;()
    map[roomData1] = 1
    println(map.containsKey(roomData2)) // true
}&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;객체를 문자열로 나열, toString()&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toString을 재정의하지 않고 객체를 출력하고자한다면..?&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;var room: Room = Room(&quot;원룸&quot;, 300_000)
println(room.toString()) // Room@3054cb8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 값이 출력됩니다. 알아볼 수 있는 부분은 Room 클래스 타입이라는 것 뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data 클래스의 객체 인스턴스를 출력한다면..?&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;var roomData: RoomData = RoomData(&quot;원룸&quot;, 300_000)
println(roomData.toString()) // RoomData(roomType=원룸, price=300000)&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;&amp;nbsp;&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;- 데이터 클래스의 copy() 메소드를 활용하여 값을 쉽게 복사할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- copy 함수를 통해 해당 데이터 클래스를 불변으로 유지할 수 있다. 하지만 내부 변수를 &lt;b&gt;val&lt;/b&gt;로 선언해야 불변이라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 해당 data 클래스의 인스턴스가 불변이라는 점이 깨지게 된다면 hash 계열의 자료구조 구현체를 활용하게 되는 경우 의도치 않게 버그를 마주칠 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 불변하지 않도록 변수를 var로 선언한 Room 클래스를 보자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;data class Room(var roomType: String, val price: Int)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;price 변수는 불변, roomType 변수는 가변으로 선언해두었다. 이로 인해 roomType 값은 Room 클래스의 인스턴스 안에서 자유롭게 변경할 수 있게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {

    var room: Room = Room(&quot;원룸&quot;, 300_000)
    var set = hashSetOf(room);

    println(set.contains(room)) // true

    // roomType 변수의 값을 변경
    room.roomType = &quot;투룸&quot;
    println(set.contains(room)) // false

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;roomType을 변경하는 순간. hashSet 자료구조에서는 컬렉션 내부에 키값으로 &quot;원룸,300_000&quot;으로 가지고 있게 됩니다. value로 해당 room의 인스턴스를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 해당 키값에 해당하는 hashCode가 &quot;투룸,300_000&quot; 변경되었으니 equals도 false, hashCode 값도 다르게 리턴되는 상황이 발생하게 되었습니다. 이러한 버그는 개발자가 의도하지 않았더라도 다른 개발자와의 협업에서 충분히 발생할 수 있는 이슈입니다.&lt;/p&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;이런 전처로 기존 객체를 수정하기보다는 새로운 객체를 복사해서 사용하는 것이 좋습니다. 이를 위해 copy 함수를 활용합니다.(물론 내부 변수들은 val로 선언해두어야 합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;copy 함수는 원하는 프로퍼티만 변경하여 복사할 수도 있습니다.(named argument 활용)&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {

    var room: Room = Room(&quot;원룸&quot;, 300_000)
    var set = hashSetOf(room);

    println(set.contains(room)) // true

    // roomType 변수의 값을 변경하여 복사
    val roomTypeTwoRoom = room.copy(roomType = &quot;투룸&quot;)
    println(set.contains(room)) // true
    println(roomTypeTwoRoom.roomType) // 투룸

}&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;내부 변수를 선언된 순서대로 가져오는 componentN 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;componentN은 enum의 ordinal 처럼 선언된 내부 변수의 값을 순서대로 가져올 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    var room: Room = Room(&quot;원룸&quot;, 300_000)
    println(room.component1()) // 원룸
    println(room.component2()) // 300_000
}&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;b&gt;구조분해할당&lt;/b&gt;이라는 문법을 사용하여 더 쉽고 안전하게 변수를 선언할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;val (roomType, price) = room
println(&quot;$roomType, $price&quot;) // 원룸, 300000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드가 동작할 때 room의 roomType변수에는 component1() 함수가, price 변수에는 component2() 함수가 사용되게 됩니다.&lt;/p&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 class=&quot;kotlin&quot;&gt;&lt;code&gt;val (price2, roomType2) = room
println(&quot;$roomType2, $price2&quot;) // 300000, 원룸&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언된 변수의 갯수와 순서에 따라서 호출되는 componentN 함수가 일정한 것을 볼수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 선언된 변수의 갯수를 넘기면 어떻게 될까요?? title이라는 세번째 변수를 추가해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;// Destructuring declaration initializer of type Room must have a 'component3()' function
val (roomType, price, title) = room
println(&quot;$roomType, $price&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 component3() 함수가 없다면서 컴파일이 되지 않습니다.&lt;/p&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <category>코틀린 componentN</category>
      <category>코틀린 copy</category>
      <category>코틀린 data 클래스</category>
      <category>코틀린 equals hashCode toString</category>
      <category>코틀린 구조분해할당</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/477</guid>
      <comments>https://sas-study.tistory.com/477#entry477comment</comments>
      <pubDate>Fri, 30 Sep 2022 20:31:47 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] Collection 컬렉션(List, Map, Set 등)</title>
      <link>https://sas-study.tistory.com/476</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Kotlin Collection&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Collection Types&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자체적으로 기본 컬렉션 타입인 List, Set, Map을 제공하고 있습니다.&lt;/p&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;- 가변 컬렉션(mutable) : 자바의 컬렉션처럼 가변적으로 삽입, 수정, 삭제 작업이 가능한 컬렉션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 불변 컬렉션(immutable) : 자바의 unmodifiableList 처럼 해당 컬렉션의 인스턴스 뿐만아니라 내부 값까지도 불변하도록 처리하는 컬렉션 타입&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;979&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UIS7q/btrNvsgRYb8/NsjYV8f2d6anRcYWKEykMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UIS7q/btrNvsgRYb8/NsjYV8f2d6anRcYWKEykMK/img.png&quot; data-alt=&quot;kotlin collection architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UIS7q/btrNvsgRYb8/NsjYV8f2d6anRcYWKEykMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUIS7q%2FbtrNvsgRYb8%2FNsjYV8f2d6anRcYWKEykMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;979&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;979&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kotlin collection architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;b&gt;2. 컬렉션 생성하기.&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Immutable &lt;u&gt;&lt;b&gt;List&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val roomTypeList = listOf(&quot;원룸&quot;, &quot;투룸&quot;, &quot;아파트&quot;, &quot;오피스텔&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 해당 list의 참조를 확인해보았을 때, 추가, 수정, 삭제 함수가 없다. -&amp;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;Mutable &lt;u&gt;&lt;b&gt;List&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;val mutableRoomTypeList = mutableListOf&amp;lt;String&amp;gt;()
mutableRoomTypeList.add(&quot;원룸&quot;)
mutableRoomTypeList.add(&quot;투룸&quot;)
mutableRoomTypeList.add(&quot;아파트&quot;)
mutableRoomTypeList.add(&quot;오피스텔&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이런식으로 직접 add 메소드로 추가하는 방식이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이 방법보다는 아래의 방법을 많이 추천!&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;val mutableRoomTypeList = mutableListOf&amp;lt;String&amp;gt;().apply {
    add(&quot;원룸&quot;)
    add(&quot;투룸&quot;)
    add(&quot;아파트&quot;)
    add(&quot;오피스텔&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- apply 함수를 사용하면 불필요한 참조 호출 부분은 코드에서 사라진다.&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;Immutable &lt;u&gt;&lt;b&gt;Set&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;val numberSet = setOf(1,2,3,4)&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;Mutable &lt;u&gt;&lt;b&gt;Set&lt;/b&gt;&lt;/u&gt; (apply 함수 활용)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;mutableSetOf&amp;lt;Int&amp;gt;().apply { 
    add(1)
    add(2)
    add(3)
    add(4)
}&lt;/code&gt;&lt;/pre&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;Immutable &lt;u&gt;&lt;b&gt;Map&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cal&quot;&gt;&lt;code&gt;var numberMap = mapOf(&quot;one&quot; to 1, &quot;two&quot; to 2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이런식으로 key와 value를 직접 &lt;b&gt;to&lt;/b&gt; 키워드를 사용하여 지정해줄 수 있다.(to 키워드를 &lt;u&gt;중위 함수&lt;/u&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;Mutable &lt;u&gt;&lt;b&gt;Map&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val mutableNumberMap = mutableMapOf&amp;lt;String, Int&amp;gt;()
mutableNumberMap[&quot;one&quot;] = 1 // 선호
mutableNumberMap.put(&quot;two&quot;, 2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위처럼 두가지의 표현이 가능한데, 두번째는 Java와의 형태적 유사함을 가져갔지만 코틀린에서는 첫번째 표현을 선호한다. 가독성이 더 좋다.&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;b&gt;컬렉션 빌더를 통한 생성.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- buildList, buildSet, buildMap 3가지 종류의 컬렉션 빌더를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 해당 함수의 &lt;u&gt;내부에서는 &lt;b&gt;가변&lt;/b&gt;(mutable)&lt;/u&gt;으로 동작하고 &lt;u&gt;결과로써 return된 이후로는 &lt;b&gt;불변&lt;/b&gt;(immutable)&lt;/u&gt;으로 동작한다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;val buildList: List&amp;lt;String&amp;gt; = buildList {
    // 이 안에서는 마음껏 넣을 수있지만 한번 리턴되면 immutable 하게 변경된다.
    add(&quot;원룸&quot;)
    add(&quot;투룸&quot;)
    add(&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;- 특정 구현체를 사용하기 위해서는 직접 구현해주면 된다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;// linkedList
val linkedList = LinkedList&amp;lt;String&amp;gt;().apply {
    addFirst(&quot;원룸&quot;) // 이렇게 컬렉션 고유의 함수도 사용가능하다.
    add(&quot;투룸&quot;)
    addLast(&quot;쓰리룸&quot;)
}
// arrayList
val arrayList = ArrayList&amp;lt;String&amp;gt;().apply {
    add(&quot;원룸&quot;)
    add(&quot;투룸&quot;)
    add(&quot;쓰리룸&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;b&gt;3. 컬렉션 반복하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Java의 java.util.Collection 인터페이스처럼 Kotlin의 Collection 인터페이스 또한 Iterable 인터페이스를 상속하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 따라서 Iterator 반복자를 이용한 루핑이 가능하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Iterator&lt;/b&gt; 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val roomTypeList = listOf(&quot;원룸&quot;, &quot;투룸&quot;, &quot;아파트&quot;, &quot;오피스텔&quot;)
val iterator = roomTypeList.iterator()
while(iterator.hasNext()) {
    println(iterator.next())
}
// 원룸
// 투룸
// 아파트
// 오피스텔&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;&lt;b&gt;foreach&lt;/b&gt;문을 통한 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;for (roomType in roomTypeList) {
    println(roomType)
}&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;&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;b&gt;forEach{ }&lt;/b&gt; 함수를 통한 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;roomTypeList.forEach {
    println(it)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- it이란 키워드는 코틀린에서 forEach { } 함수 내에서 루핑 컬렉션의 요소를 지칭하는 키워드이다. (forEach{ } 같은 함수를 &lt;u&gt;인라인 함수&lt;/u&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;b&gt;foreach문&lt;/b&gt;으로 변환하기&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;val lowerList = listOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)
val upperList = mutableListOf&amp;lt;String&amp;gt;()
for (word in lowerList) {
    upperList.add(word.uppercase())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;map { }&lt;/b&gt; 인라인 함수로&amp;nbsp;변환하기&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;val lowerList = listOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)
lowerList.map { it.uppercase() }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- code가 간단해서 많이 차이나진 않지만 행위 자체가 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;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val filteredList = mutableListOf&amp;lt;String&amp;gt;()
for (upperCase in upperList) {
	if (upperCase == &quot;A&quot; || upperCase == &quot;C&quot;) {
	  filteredList.add(upperCase)
	}
}
println(filteredList)
// 출력결과 : [A, C]&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;val filteredList = upperList.filter { it == &quot;A&quot; || it == &quot;C&quot; }
println(filteredList)
// [A, C]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &quot;A&quot; 이거나 &quot;C&quot;일때만 가져오겠다는 것이 직관적이다.&lt;/p&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;이러한 코틀린 인라인 함수를 Java를 써봤던 사람이라면 조금 익숙할지 모르겠다. 더 자세히 말하면 JDK 1.8 이상 버전을 써보신 분 그리고 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;하지만 java 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의 경우 filter, map, flatMap 등의 메소드는 체이닝을 통해 해당 연산을 지연한다. 따라서 collect() 같은 최종 연산자를 호출해주지 않으면 이전 연산들이 실행되지 않게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 인라인 함수는 매번 최종 연산을 진행하기 때문에 연산 지연이 일어나지는 않지만 계속 collection을 생산해내게 된다. 그래서 코틀린에서는 filter, map 같은 함수를 사용할 때 체이닝을 통해서 진행하게 된다면 매번 새로운 collection 인스턴스를 생성해내기 때문에 heap에 메모리가 계속 쌓이게 될것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;val filteredList = upperList
    .filter { it == &quot;A&quot; || it == &quot;C&quot; }
    .map {it.uppercase()}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 코틀린에서 위의 코드는 (upperList 1개), (filter된 upperList 1개), (uppercase가 적용된 upperList 1개 = filteredList) 이런 식으로 heap에 메모리가 쌓이게 된다. -&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;그럼 코틀린에서는 이런 stream 처럼 Lazy 하게 동작시킬 수는 없을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(잠시 설명하자면 코틀린 collection에서 .stream()으로 호출하는 것은 엄연히 Java 클래스의 Stream이다. 따라서 코틀린 자체에서 동작한다고 보기는 어렵다.)&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664528453808&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val filteredList = upperList
        .asSequence()
        .filter { it == &quot;A&quot; || it == &quot;C&quot; }
        .toList()

println(filteredList)
// [A, C]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이런식으로 asSequance() 함수를 컬렉션 초입에 선언해준다.&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;u&gt;&lt;b&gt;대량의 컬렉션&lt;/b&gt;&lt;/u&gt;을 다루게 된다면 시퀀스 api를 사용하는게 타당해 보인다.&lt;/p&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <category>kotlin collection</category>
      <category>kotlin 인라인 함수</category>
      <category>코틀린 asSequence</category>
      <category>코틀린 forEach</category>
      <category>코틀린 컬렉션</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/476</guid>
      <comments>https://sas-study.tistory.com/476#entry476comment</comments>
      <pubDate>Fri, 30 Sep 2022 18:03:14 +0900</pubDate>
    </item>
    <item>
      <title>Java Switch문 정리하기.</title>
      <link>https://sas-study.tistory.com/475</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;switch 문을 사용하는 여러 예제들을 정리해보자!&lt;/p&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;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum RoomType {
    BILLA, OFFICETEL, APT;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 enum이 있을 때, switch문이 없는 경우 아래처럼 코드를 짤 수 있을 것입니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public void exampleOfIf(RoomType roomType) {
    if (roomType == RoomType.APT) {
        System.out.println(&quot;아파트&quot;);
    } else if (roomType == RoomType.BILLA) {
        System.out.println(&quot;일반주택&quot;);
    } else if (roomType == RoomType.OFFICETEL) {
        System.out.println(&quot;오피스텔&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 코드를 짜는 경우는 보기도 매우 어렵고 각 if 조건을 만족하지 않게 된다면(&lt;u&gt;else if 의 마지막 조건 혹은 else절까지 가게 된다면&lt;/u&gt;) 모든 조건을 검사하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public void exampleOfSwitch(RoomType roomType) {
    switch(roomType) {
        case APT:
            System.out.println(&quot;아파트&quot;);
            break;
        case BILLA:
            System.out.println(&quot;일반주택&quot;);
            break;
        case OFFICETEL:
            System.out.println(&quot;오피스텔&quot;);
            break;
        default:
            throw new RuntimeException(&quot;해당 타입 없음.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 간단하게&lt;u&gt;(사실 아직은 간단하지 않습니다.)&lt;/u&gt; switch문으로 처리하게 되는 경우 if문처럼 각 조건을 모두 검사하지 않고 해당하는 타입으로 바로 실행로직이 점프하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;break문&lt;/b&gt;은 해당 case문을 탈출할 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;break문을 case절마다 사용하지 않고 서로다른 case간에 같은 로직을 실행하고 싶은 경우. 아래와 같이 제어할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-6696145815750706&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1664357987988&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void exampleOfSwitch(RoomType roomType) {
    switch(roomType) {
        case APT:
        case BILLA:
            System.out.println(&quot;아파트와 일반주택&quot;);
            break;
        case OFFICETEL:
            System.out.println(&quot;오피스텔&quot;);
            break;
        default:
            throw new RuntimeException(&quot;해당 타입 없음.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;switch / case 데이터 타입&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;데이터 타입&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch 문의 인자로 전달 가능한 타입은 제한되어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;byte, short,&lt;span&gt;&amp;nbsp;&lt;/span&gt;int,&lt;span&gt;&amp;nbsp;&lt;/span&gt;char&lt;/li&gt;
&lt;li&gt;Byte, Short, Integer, Character (jdk 5 이후)&lt;/li&gt;
&lt;li&gt;enum (jdk 5 이후)&lt;/li&gt;
&lt;li&gt;String&amp;nbsp; (jdk 7 이후)&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;물론 case에 선언되는 상수 또한 switch문에 넘어가는 타입과 동일해야합니다.&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;b&gt;Null 허용 안함&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swtich 문의 인자로 null 값 혹은 null 값으로 변수가 넘어가게 되는 경우에는 NullPointerException을 반환하게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;RoomType roomType = null;
exampleOfSwitch(null); 		// NullPointerException 발생
exampleOfSwitch(roomType); 	// NullPointerException 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 case 절에 null 값을 기입하게 될수 없습니다. 컴파일이 되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;case 은 항상 상수&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    String roomType = &quot;APT&quot;;
    final String apt = &quot;APT&quot;;
    String officetel = &quot;OFFICETEL&quot;;

    switch (roomType) {
        case apt: 
            System.out.println(&quot;아파트&quot;);
        case officetel: // 컴파일 불가.
            System.out.println(&quot;오피스텔&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 officetel 변수는 final, 즉 상수 취급 키워드가 붙지 않았습니다. 해당 값이 불변이어야 case 절에 사용할 수 있습니다.&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;b&gt;문자열 비교&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch문이 문자열 비교를 할때는 equals 비교를 진행하기 때문에 동일성 이슈는 발생하지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664411757178&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 roomType = &quot;APT&quot;;
    
    switch (roomType) {
        case &quot;APT&quot;:
            System.out.println(&quot;아파트&quot;);
        case &quot;OFFICETEL&quot;: 
            System.out.println(&quot;오피스텔&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 switch문의 java bytecode를 살펴보면&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: 100%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;L2&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;FRAME&amp;nbsp;APPEND&amp;nbsp;[java/lang/String&amp;nbsp;java/lang/String&amp;nbsp;I]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ALOAD&amp;nbsp;2&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LDC&amp;nbsp;&quot;APT&quot;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INVOKEVIRTUAL&amp;nbsp;java/lang/String.equals&amp;nbsp;(Ljava/lang/Object;)Z&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IFEQ&amp;nbsp;L4&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ICONST_0&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ISTORE&amp;nbsp;3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;GOTO&amp;nbsp;L4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;APT&quot; 문자열을 비교할 때 String.equals 메소드가 사용되는 모습을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Switch 표현식&lt;/b&gt;&lt;/h3&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;b&gt;새로운 switch 표현식&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jdk 12부터 새롭게 추가된 switch 표현식은 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    String roomType = &quot;APT&quot;;

    var roomTypeString = switch (roomType) {
        case &quot;APT&quot; -&amp;gt;&quot;아파트&quot;;
        case &quot;OFFICETEL&quot; -&amp;gt; &quot;오피스텔&quot;;
        default -&amp;gt; &quot;일반주택&quot;;
    };
    System.out.println(roomTypeString);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch는 이제 더이상 제어문의 역할을 하는 것이 아닌 표현식으로써 메소드에 감싸져서 특정한 값을 리턴하지 않고도 스스로 값을 리턴하여 변수에 담을 수 있게 된다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;yield 키워드 추가&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch 문이 새롭게 표현식으로써 동작이 가능하게 변경됨에 따라 기존 c/c++을 토대로 저레벨 제어문처럼 동작가능하던 switch문에 변화가 생겼습니다.&lt;/p&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 class=&quot;arduino&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    String roomType = &quot;OFFICETEL&quot;;

    var roomTypeString = getRoomTypeString(roomType);
    System.out.println(roomTypeString);
}

private static String getRoomTypeString(String roomType) {
    return switch (roomType) {
        case &quot;APT&quot; -&amp;gt;&quot;아파트&quot;;
        case &quot;OFFICETEL&quot; -&amp;gt; {
            String temp = &quot;오피스텔&quot;;
            return temp;
        }
        default -&amp;gt; &quot;일반주택&quot;;
    };
}&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;return문은 과연 함수를 종료하는 return문일지 switch문에서 값을 리턴하는 return일지에 대한 기준이 애매모호해집니다.&lt;u&gt;(이건 개발자가 코드를 보는 관점에서 애매해졌다는 것이지 실제로 동작하는 과정에서는 반드시 해당 return 문을 포함하는 메소드의 종료를 나타내는 것은 확실합니다.)&lt;/u&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;따라서 switch 표현식에서 어떤 값을 리턴할때의 리턴문이 별도로 필요해지게 됩니다. 그에 따라 &quot;생산하다, 산출하다&quot; 라는 뜻의 yield 라는 키워드가 생긴걸로 보여집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 실제 컴파일 에러가 발생하겠지만 return 문을 yield 문으로 변경해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;private static String getRoomTypeString(String roomType) {
    return switch (roomType) {
        case &quot;APT&quot; -&amp;gt;&quot;아파트&quot;;
        case &quot;OFFICETEL&quot; -&amp;gt; {
            String temp = &quot;오피스텔&quot;;
            yield temp;
        }
        default -&amp;gt; &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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 아래처럼 간단한 표현식의 경우는 위의 yield 키워드를 고민할 필요는 없겠지만 상황에 따라 여러 로직이 필요할 수 있음을 대비해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664413288924&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var roomTypeString = switch (roomType) {
        case &quot;APT&quot; -&amp;gt;&quot;아파트&quot;;
        case &quot;OFFICETEL&quot; -&amp;gt; &quot;오피스텔&quot;;
        default -&amp;gt; &quot;일반주택&quot;;
    };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-id=&quot;4-exhaustiveness&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;Exhaustiveness(철저함)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    RoomType roomType = RoomType.APT;

    String roomTypeStr = switch (roomType) {
        case APT -&amp;gt; &quot;아파트&quot;;
        case OFFICETEL -&amp;gt; &quot;오피스텔&quot;;
        case BILLA -&amp;gt; &quot;일반주택&quot;;
    };
    System.out.println(roomTypeStr);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위의 코드는 컴파일 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public static void main(String[] args) {
        RoomType roomType = RoomType.APT;

        String roomTypeStr = switch (roomType) {
            case APT -&amp;gt; &quot;아파트&quot;;
            case OFFICETEL -&amp;gt; &quot;오피스텔&quot;;
//            case BILLA -&amp;gt; &quot;일반주택&quot;;
        };
        System.out.println(roomTypeStr);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 위의 코드는 컴파일이 되지 않습니다. (error: the switch expression does not cover all possible input values)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위와 같은 에러메시지를 내보낸 후 switch 표현식이 모든 경우를 커버할 수 없다는 이야기를 합니다. 이게 무슨이야기인지 확인해보겠습니다.&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;위에서 선언했던 enum 클래스인 RoomType 클래스를 다시 확인해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crystal&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum RoomType {
    BILLA, OFFICETEL, APT;
}&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;세번째 case문에서 BILLA case를 주석처리해버렸습니다. 따라서 변수로 넘어온 roomType이라는 값에 BILLA 타입의 RoomType 인스턴스가 넘어오게 된다면 해당 switch 표현식은 별도의 default 문이 없기 때문에 반환할 값을 산정하지 못하게 됩니다.&lt;/p&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;u&gt;(필자는 여기서 if 문이 아닌 switch문을 사용해야될 이유를 하나 더 생각하게 되는 것 같습니다.)&lt;/u&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Java</category>
      <category>java switch expression</category>
      <category>java switch 정리</category>
      <category>자바 스위치 표현식</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/475</guid>
      <comments>https://sas-study.tistory.com/475#entry475comment</comments>
      <pubDate>Thu, 29 Sep 2022 10:09:58 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] kotlin enum</title>
      <link>https://sas-study.tistory.com/474</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린도 자바와 마찬가지로 enum 타입의 클래스를 제공한다.&lt;/p&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;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;enum class PaymentStatus {
    UNPAID, PAID, FAILED, REFUNDED
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바와는 다르게 enum 키워드 뒤에 class라는 키워드를 붙여주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 필드를 추가해보자. 해당 enum의 상수가 어떤 값인지를 나타내는 label 문자열 변수를 추가해보자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;enum class PaymentStatus(val label: String) {
    UNPAID(&quot;미지급&quot;), 
    PAID(&quot;지급완료&quot;), 
    FAILED(&quot;지급실패&quot;), 
    REFUNDED(&quot;환불&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 값을 출력해보자&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;fun main() {
    println(PaymentStatus.UNPAID.label) // 미지급
    println(PaymentStatus.PAID.label) // 지급완료
    println(PaymentStatus.FAILED.label) // 지급실패
    println(PaymentStatus.REFUNDED.label) // 환불
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum 값 마다 동작가능한 함수를 선언해보자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;enum class PaymentStatus(val label: String) {
    UNPAID(&quot;미지급&quot;) {
        override fun isPayable(): Boolean {
            return true
        }
    },
    PAID(&quot;지급완료&quot;) {
        override fun isPayable(): Boolean {
            return false
        }
    },
    FAILED(&quot;지급실패&quot;) {
        override fun isPayable(): Boolean {
            return false
        }
    },
    REFUNDED(&quot;환불&quot;) {
        override fun isPayable(): Boolean {
            return false
        }
    };

    abstract fun isPayable(): Boolean
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드처럼 isPayable이라는 추상메소드를 선언하고 각 enum 타입마다 해당 함수를 구현하도록 처리할 수 있다.&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;하지만 보통은 이렇게 처리하지 않고 Payable이라는 &lt;u&gt;함수형 인터페이스&lt;/u&gt;를 선언해서 구현한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;enum class PaymentStatus(val label: String): Payable{
    UNPAID(&quot;미지급&quot;) {
        override fun isPayable(): Boolean = false
    },
    PAID(&quot;지급완료&quot;) {
        override fun isPayable(): Boolean = false
    },
    FAILED(&quot;지급실패&quot;) {
        override fun isPayable(): Boolean = false
    },
    REFUNDED(&quot;환불&quot;) {
        override fun isPayable(): Boolean = false
    };
}

interface Payable {
    fun isPayable(): Boolean
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드를 사용해보자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    println(PaymentStatus.UNPAID.label) // 미지급
    println(PaymentStatus.PAID.label) // 지급완료
    println(PaymentStatus.FAILED.label) // 지급실패
    println(PaymentStatus.REFUNDED.label) // 환불

    if (PaymentStatus.UNPAID.isPayable()) {
        println(&quot;결제 가능 상태&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 코틀린 Enum에서 제공하는 valueOf, values 함수에 대해서 알아보자.&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 class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val paymentStatus = PaymentStatus.valueOf(&quot;PAID&quot;)
    println(paymentStatus.label)
    if (paymentStatus == PaymentStatus.PAID) {
        println(&quot;결제 완료 상태&quot;)
    }
    for (status in PaymentStatus.values()) {
        println(&quot;[${status.name}](${status.label}) : ${status.ordinal}&quot;)
    }
}
/*
출력결과
지급완료
결제 완료 상태
[UNPAID](미지급) : 0
[PAID](지급완료) : 1
[FAILED](지급실패) : 2
[REFUNDED](환불) : 3
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;valueOf&lt;/b&gt; 함수는 String 값을 enum의 상수 value와 같은 값이 있는지를 찾는 함수이다. 맞는 문자열 값이 있다면 해당 enum 의 인스턴스를 리턴한다.&lt;/p&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;values&lt;/b&gt; 함수는 enum에 선언된 모든 상수 인스턴스 List 컬렉션을 반환하여 loop를 돌릴 수 있도록 도와준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- name으로 접근하는 것은 UNPAID와 같은 enum string에 접근하는 것이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ordinal로 접근하는 것은 선언된 순서를 zero based index 기반으로 접근하는 것이다.(이건 사실 굳이 쓸 일이 없다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <category>kotlin enum</category>
      <category>코틀린 enum</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/474</guid>
      <comments>https://sas-study.tistory.com/474#entry474comment</comments>
      <pubDate>Thu, 29 Sep 2022 07:48:10 +0900</pubDate>
    </item>
    <item>
      <title>HV000030: No validator could be found for constraint 'javax.validation.constraints.NotBlank'  에러 수정.</title>
      <link>https://sas-study.tistory.com/473</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP Request 요청 정보중에서&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;Query Param&lt;/li&gt;
&lt;li&gt;Request Body&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;위 두가지의 유효성 검사를 위해서 javax.validation.contraints 패키지 내의 @NotNull, @NotEmpty, @NotBlank 등의 어노테이션을 활용하는 경우가 있다.&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;@NotNull은 Nullable한 타입에 선언해야하는데.. 왜냐하면 int나 char 같은 primitive 타입은 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;이와 마찬가지로 @NotBlank와 @NotEmpty도 마찬가지로 유효성 검사를 진행할 수 있는 타입이 존재한다.&lt;/p&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;@NotBlank&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 하나의 공백 문자열도 포함하지 않는다. -&amp;gt; 오로지 문자열에 해당함.(String, CharSequance)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;주석내용 : The annotated element must not be null and must contain at least one non-whitespace character.&lt;/u&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;@NotEmpty&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CharSequence (length of character sequence is evaluated)&lt;/li&gt;
&lt;li&gt;Collection (collection size is evaluated)&lt;/li&gt;
&lt;li&gt;Map (map size is evaluated)&lt;/li&gt;
&lt;li&gt;Array&amp;nbsp;(array&amp;nbsp;length&amp;nbsp;is&amp;nbsp;evaluated)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;empty의 경우 empty라는 키워드가 들어갈 수 있는 컬렉션이라던가 문자열 타입만 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우 @NotBlank 어노테이션을 enum 클래스에 선언하여 아래와 같은 에러를 뱉어냈다.&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;javax.validation.UnexpectedTypeException:&amp;nbsp;HV000030:&amp;nbsp;No&amp;nbsp;validator&amp;nbsp;could&amp;nbsp;be&amp;nbsp;found&amp;nbsp;for&amp;nbsp;constraint&amp;nbsp;'javax.validation.constraints.NotBlank'&amp;nbsp;validating&amp;nbsp;type&amp;nbsp;'co&lt;a href=&quot;http://m.dabangapp.sign.enums.SignableRoomSearchKeywordType'.&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;m.xxx.xxx.enums.SignableRoomSearchKeywordType'.&lt;/a&gt;&amp;nbsp;Check&amp;nbsp;configuration&amp;nbsp;for&amp;nbsp;'keywordType'&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keywordType 이라는 enum 변수에 @NotBlank를 사용하니 해당 타입을 validate할 수 있는 validator가 없다는 에러를 낼 수 밖에 없다.&lt;/p&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>에러모음</category>
      <category>@NotBlank</category>
      <category>@NotEmpty</category>
      <category>spring bean validator</category>
      <category>spring validator</category>
      <category>Validator</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/473</guid>
      <comments>https://sas-study.tistory.com/473#entry473comment</comments>
      <pubDate>Tue, 27 Sep 2022 14:28:36 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] 상속(inheritance) 관련 기본 내용</title>
      <link>https://sas-study.tistory.com/472</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 class 타입은 기본적으로 final 입니다.&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;final 키워드가 클래스에 붙게 되는 경우 해당 클래스는 상속을 이어갈 수 없는 클래스가 됩니다.&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;자바에서는 모든 클래스가 상속이 가능하며 final 키워드를 활용하여 추후 상속가능성을 닫는 구조였습니다. 하지만 kotlin은&lt;span&gt; class가 상속을 허용해주는 방향으로 발전해나갔으며 해당 키워드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;open&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;입니다.&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1664072593332&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class Dog { // 상속이 가능한 상태의 클래스
    open var age: Int = 0
    open fun bark() {
        println(&quot;멍멍&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dog 클래스의 필드, 함수들 또한 클래스와 마찬가지로 open 키워드를 통해 오버라이딩을 허용해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 Dog 클래스를 상속하는 ChildDog 클래스를 만들어보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;open class ChildDog(final override var age: Int = 0): Dog() {

    final override fun bark() {
        println(&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이제 ChildDog을 상속하는 DescendatDog을 만들어보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class DescendatDog: ChildDog() {

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 클래스는 ChildDog의 필드와 함수를 오버라이딩 할 수 없기 때문에 빈 몸통만 남았습니다. 왜냐하면 ChildDog 클래스의 필드와 함수가 모두 final 키워드로 정의되어 있기 때문입니다.(직접 명시된 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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;추상클래스&lt;/b&gt;&lt;/h4&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;먼저 개발자라는 뜻의 Developer라는 추상 클래스를 정의해보고 age라는 필드와 code라는 함수를 정의해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;abstract class Developer {
    abstract var age: Int
    abstract fun code(language: String)
}&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;이제 해당 클래스를 구현하는 BackendDeveloper라는 구현 클래스를 정의해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class BackendDeveloper(override var age: Int = 0): Developer() {

    override fun code(language: String) {
        println(&quot;I write code with $language&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상클래스도 일반 클래스를 상속하듯이 동일한 내용이 적용됩니다. 그리고 abstract 키워드 또한 자바와 동일합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <category>코틀린 final open</category>
      <category>코틀린 상속</category>
      <category>코틀린 추상클래스</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/472</guid>
      <comments>https://sas-study.tistory.com/472#entry472comment</comments>
      <pubDate>Sun, 25 Sep 2022 11:32:16 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] 클래스(Class) 관련 기본내용</title>
      <link>https://sas-study.tistory.com/471</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;클래스 (Class)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 클래스를 알기 전에 코틀린 파일의 특징부터 알아야 합니다. 코틀린 파일은 &lt;b&gt;xxx.kt&lt;/b&gt; 형태로 작성하며 안쪽 구조는 java와는 다소 큰 차이가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 메소드가 kt 파일 내에 클래스의 존재와 관련없이 존재할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664070589845&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 일반 클래스
class Test(val test: Int) {

}

// 클래스와 떨어진 함수.
fun test() {
	println(&quot;test&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 일반 클래스에서 (val test: Int) 표현은 생략된 생성자 표현입니다. 생성자는 constructor 키워드로 아래와 같이 표현할 수도 있습니다. 하지만 표현상 constructor를 제외한 표현이 더욱 간소화되기도 하고 가독성도 좋기 때문에 대체로 생략하는 편입니다.&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;class Test constructor(val test: Int) {
    
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;1) 클래스&lt;/u&gt;는 본문이 없이 존재할 수 있습니다.(Empty Class)&lt;/p&gt;
&lt;pre id=&quot;code_1664070706323&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 일반 클래스
class Test(val test: Int) {

}

// 클래스와 떨어진 함수.
fun test() {
	println(&quot;test&quot;)
}

// 본문이 없는 Empty Class
class EmptyClass&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;u&gt;2) 클래스&lt;/u&gt;의 생성자는 후행쉼표를 제공합니다. 따라서 이에 대한 이점을 많이 제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664070978086&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 클래스 생성자 후행쉼표 제공
class Test(val test: Int,
           val test2: Int,
           ) {

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 코딩스타일의 경우 생성자의 인자가 늘어날 경우에 대한 대비가 되어있다고 표현할 수 있고, Github를 통해 코드리뷰를 진행할 경우 변경 사항에 대해서 불필요한 콤파에 의한 변경사항 감지를 느낄 필요가 없습니다.&lt;/p&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;u&gt;3) 클래스&lt;/u&gt;는 생성자에 default 값을 설정할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664071118843&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Coffee (
    var name: String = &quot;&quot;, // 기본값
    var price: Int = 0,
    var iced: Boolean = false,
)&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) 클래스는 기본적으로 getter/setter가 내장되어 있으며 이를 직접 구현할수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class Coffee (
    var name: String = &quot;&quot;, // 기본값
    var price: Int = 0,
    var iced: Boolean = false,
){

    // custom getter
    val brand: String
        get() {
            return &quot;내가 만든 브랜드.&quot;
        }

    var quantity: Int = 0
        set(value) {
            if (value &amp;gt; 0) {
                // field 식별자. -&amp;gt; quantity를 호출하게 되면 stackoverflow 터진다.
                field = value
            }
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 setter 부분에서 &lt;b&gt;&lt;u&gt;field&lt;/u&gt;&lt;/b&gt;라는 키워드가 있는데요. 해당 키워드는 quantity 자체의 프로퍼티를 지칭하는 대명사 키워드로 여기서 직접 quantity를 호출하게 되는경우(quantity = value) 또한번 setter가 호출되어 호출스택의 setter 재귀가 무한대로 늘어나 stackoverflow가 발생하게 됩니다. 이에 주의해야합니다.&amp;nbsp;&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;&lt;b&gt;setter에서는 해당 필드에 대입 및 접근하기 위해서는 field라는 제공된 키워드를 사용해야 합니다.&lt;/b&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;클래스의 인스턴스에서 필드를 호출할 때는 getter setter 기반으로 호출됩니다. 위에서 사용된 클래스를 살짝 수정해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class Coffee (
    var name: String = &quot;&quot;, // 기본값
    var price: Int = 0,
    var iced: Boolean = false,
){

    // custom getter
    val brand: String
        get() {
            println(&quot;getter가 사용됨.&quot;)
            return &quot;내가 만든 브랜드.&quot;
        }

    var quantity: Int = 0
        set(value) {
            if (value &amp;gt; 0) {
                println(&quot;setter가 사용됨.&quot;)
                // field 식별자. -&amp;gt; quantity를 호출하게 되면 stackoverflow 터진다.
                field = value
            }
        }
}

fun main() {
    val coffee = Coffee()
    coffee.quantity = 1

    println(&quot;수량은 ${coffee.quantity} 브랜드는 ${coffee.brand}&quot;) // getter
}
// setter가 사용됨.
// getter가 사용됨.
// 수량은 1 브랜드는 스타벅스&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;quantity 변수에 직접 값을 할당할때, coffee.brande를 출력할 때 &lt;u&gt;&lt;b&gt;getter와 setter가 사용&lt;/b&gt;&lt;/u&gt;되는 것을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <category>코틀린 getter/setter</category>
      <category>코틀린 setter fieid</category>
      <category>코틀린 setter stackoverflow</category>
      <category>코틀린 클래스</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/471</guid>
      <comments>https://sas-study.tistory.com/471#entry471comment</comments>
      <pubDate>Sun, 25 Sep 2022 11:08:17 +0900</pubDate>
    </item>
    <item>
      <title>물리삭제와 논리삭제</title>
      <link>https://sas-study.tistory.com/470</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;SQL을 배울때 기본적으로 특정 테이블의 레코드를 삭제처리할 때는 DELETE 쿼리를 아래와 같이 실행할 것입니다. (&lt;b&gt;물리삭제&lt;/b&gt;)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;DELETE FROM table WHERE seq = 1&amp;nbsp; -- 물리삭제 쿼리&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 제가 처음 실무를 접했을 때, 많은 테이블에서 DELETE 쿼리를 실행하지 않고 삭제처리를 하고 있었습니다. DELETE 쿼리를 실행하지 않고 어떻게 삭제처리를 할 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 논리적인 개념의 컬럼을 추가하고 해당 컬럼을 UPDATE 실행을 하면 삭제되었다고 여기는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 해당 &lt;b&gt;논리 삭제&lt;/b&gt;의 UPDATE문 입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;UPDATE table is_delete = true, delete_time = now() WHERE seq = 1 -- 논리삭제 쿼리&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;왜 논리삭제를 하는가?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 다양한 이유가 있겠지만 가장 우선적으로 떠오르는 것은 &lt;u&gt;데이터가 유지되어야하는 속성의 테이블&lt;/u&gt;의 경우에는 논리삭제로 해당 테이블 레코드의 가장 마지막 순간을 남겨놓는 것으로 생각됩니다. 예를 들어, 특정 도메인을 관리하는 화면에서 해당 도메인 레코드를 삭제했다고 하더라도 운영자의 입장에서는 해당 유저가 &lt;u&gt;특정 시각에 특정 도메인 레코드를 &lt;b&gt;삭제&lt;/b&gt;했다는 사실은 인지&lt;/u&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;논리삭제의 장점과 단점에 대해서 알아보겠습니다. 논리삭제의 장/단점은 반대로 물리삭제의 단/장점이 될 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;논리삭제의 &lt;u&gt;장점&lt;/u&gt;은 무엇인가?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논리삭제는 아무래도 삭제된 데이터를 SELECT로 조회할 수 있기 때문에 물리적으로 삭제하는 물리삭제보다는 조금더 &lt;u&gt;데이터 관리상 안전&lt;/u&gt;한 편에 속합니다.&amp;nbsp;&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;b&gt;논리삭제의 &lt;u&gt;단점&lt;/u&gt;은 무엇인가?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점은 아무래도 물리적으로 한번 쌓인 데이터는 삭제하지 못하는 구조이기 때문에 &lt;u&gt;데이터베이스의 용량이 매우 비대해질 수 있습니다&lt;/u&gt;. 따라서 해당 테이블이 물리삭제 대상인지 논리삭제 대상인지에 대한 적절한 구분이 반드시 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 논리삭제는 DB 스키마 구조에서 &lt;u&gt;불필요한 컬럼&lt;/u&gt;을 하나 차지하게 되므로 검색조건시에 삭제처리된 레코드의 경우 반드시 필터해주어야 하기 때문에 SELECT 조회시 불필요한 &lt;u&gt;검색조건을 추가&lt;/u&gt;해야 합니다. 또한 SELECT 쿼리의 조회 성능 향상을 위해 반드시 index 필드를 설정해두어야 할텐데 해당 index 필드셋에 논리삭제 컬럼이 반드시 추가되어야 할 것입니다.(WHERE 랜덤 엑세스 최소화를 위해, 랜덤엑세스에 대한 자료는&amp;nbsp;&lt;a href=&quot;https://sas-study.tistory.com/448&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;어떤 테이블 데이터를 물리삭제/논리삭제 해야하는가?&lt;/b&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;하지만 테이블의 레코드가 특정 상황에서만 필요하고 기록의 의미로써 유효하지 않다면 단순히 물리삭제로 처리해도 좋을 것 같습니다. 간단한 예시로는 특정 도메인간의 매핑이 있을 것 같습니다. 예를 들어, 통신사 가족결합을 한다고 했을 때, 부모님과 저의 관계는 가족결합이라는 서비스에 가입했을 때 유효합니다. 따라서 가족결합을 해지할 경우에 해당 번호간의 관계는 제거될 수 있으므로 &lt;b&gt;가족결합 관계 매핑 테이블&lt;/b&gt;의 경우는 &lt;u&gt;물리삭제&lt;/u&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;테이블에서 컬럼으로 삭제처리하는 것만이 논리삭제라고 할 수 있는가?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니라고 생각한다. 위에서 가족결합 관계 매핑 테이블에서 기존 가족결합 상품 가입기록을 확인해야할 필요가 있다면 별도의 history 테이블에 쌓을 수 있습니다. 이러한 경우도 결국 특정 테이블에서는 물리적으로 삭제되지만 결과적으로 삭제된 내용을 다른 테이블에 기록하기 때문에 일종의 논리적 삭제라고 볼 수 있을 것 같습니다.&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;u&gt;(하지만 특정 테이블을 기준으로 한다면 물리삭제가 맞겠네요.)&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 동일한 스키마 구조를 가진 table과 delete_table을 유지하는 방법도 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>흑구의 공부내용 공유</category>
      <category>논리삭제</category>
      <category>물리삭제</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/470</guid>
      <comments>https://sas-study.tistory.com/470#entry470comment</comments>
      <pubDate>Fri, 23 Sep 2022 10:30:47 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] Exception, try-catch 구문</title>
      <link>https://sas-study.tistory.com/469</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 Exception은 아래의 구조를 가진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOO85J/btrMPVxhYBB/lRuKPlRfIBBtka2Dhjx01K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOO85J/btrMPVxhYBB/lRuKPlRfIBBtka2Dhjx01K/img.png&quot; data-alt=&quot;에러 구조.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOO85J/btrMPVxhYBB/lRuKPlRfIBBtka2Dhjx01K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOO85J%2FbtrMPVxhYBB%2FlRuKPlRfIBBtka2Dhjx01K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;850&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;850&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;Error는 말그대로 프로그램을 더이상 운영할 수 없는 정도의 아주 크리티컬한 에러이고, Exception은 우리가 catch 해야할 Exception의 유형들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서 CheckedException을 처리하는 방법이 Kotlin과 Java와의 차이가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1663886464044&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Test {
    public static void main(String[] args) {
    	Thread.sleep(1)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서 위 코드는 컴파일 에러를 뱉는다. 왜냐하면 sleep 메소드는 checkedException을 전파하는 메소드이고 메인 메소드가 이에 대해서 별도로 처리하지 않았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1663886525714&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
	Thread.sleep(1)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Kotlin 언어에서 위 코드는 무사히 컴파일도 통과하고 런타임도 통과하여 1ms의 sleep을 실행한다. 코틀린에서는 CheckedException을 강제로 처리하지 않아도 된다. 필요한 경우에 한하여 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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;try-catch-finally 구문&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린도 자바와 마찬가지로 예외처리할 수 있는 try-catch문을 제공한다. 물론 finally 문도 제공한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;try {
    throw Exception()
} catch (e: Exception) {
    println(&quot;에러발생!&quot;)
} finally {
    println(&quot;finally 실행!&quot;)
}
// 에러발생!
// finally 실행!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지는 자바와 크게 다르지 않지만 if문과 동일하게 &lt;b&gt;try-catch 구문은 &lt;u&gt;표현식&lt;/u&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;표현식처럼 동작하는 try-catch문&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val a = try {
    &quot;123&quot;.toInt()
} catch (e: Exception) {
    println(&quot;예외발생!&quot;)
}
println(a)
// 123&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;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun exceptionMethod(message: String) { 
    throw IllegalArgumentException(message)
}&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;해당 메소드는 호출하자마자 IllegalArgumentException을 던진다. 리턴타입을 보면 Nothing이라는 타입을 리턴하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태는 아무것도 리턴하지 않는 Java에서 void 키워드로 동작하는 메소드 같지만 사실은 Nothing이라는 타입을 리턴한다. 이와 비슷한 리턴타입으로 Unit 타입이 있다. Unit과 Nothing의 차이점은 &lt;u&gt;불확실함&lt;/u&gt;이다. 쉽게 이야기하면 해당 코드에서 명시적으로 throw 만 일으키는 유형의 함수에 리턴타입 Nothing이 붙는다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1663887110035&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun exceptionMethod(message: String): Nothing{ 
    throw IllegalArgumentException(message)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 리턴타입이 붙는 함수이다.&lt;/p&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <category>kotlin nothing</category>
      <category>코틀린 checkedException</category>
      <category>코틀린 exception</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/469</guid>
      <comments>https://sas-study.tistory.com/469#entry469comment</comments>
      <pubDate>Fri, 23 Sep 2022 07:52:29 +0900</pubDate>
    </item>
    <item>
      <title>[코클린 기초] 코틀린 널 안정성(Null Safety)</title>
      <link>https://sas-study.tistory.com/468</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Null 참조의 위험성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바를 포함한 많은 프로그래밍 언어에서 가장 많이 발생하는 예외가 바로 &lt;u&gt;&lt;b&gt;NullPointerException&lt;/b&gt;&lt;/u&gt;일 것입니다. 줄여서 NPE라고도 합니다. Null을 발명한 토니호어는 1965년 null을 발명한 것이 자신의 1조원짜리 &lt;u&gt;실수&lt;/u&gt;였다고 고백합니다. 왜냐하면 null로 인해서 발생한 오류와 피해가 수십년간 수십억 달러에 달하기 때문이라고 합니다.&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;b&gt;자바&lt;/b&gt;에서는 이러한 Null로 인해 발생하는 NPE를 줄이기 위해 &lt;u&gt;Optional&lt;/u&gt; 이라는 클래스 타입을 제공하기 시작했습니다. 하지만 자바에서 아무리 Optional을 사용한다고 하더라도 Optional은 타겟값을 감싸는 클래스이기 때문에 그에 대한 오버헤드가 발생하고 컴파일을 하는 단계에서는 문법적 요소만 통과한다면 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;u&gt;&lt;b&gt;코틀린&lt;/b&gt;&lt;/u&gt;을 비롯한 최신 언어에서는 &lt;b&gt;컴파일러&lt;/b&gt;가 이러한 &lt;u&gt;Null 참조 가능성에 대한 부분까지 검사&lt;/u&gt;해주기 때문에 NPE 가능성에 대해서 기존 언어에 대비하여 많이 줄일 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin에서는 문법적으로 &lt;u&gt;Null을 &lt;b&gt;허용&lt;/b&gt;하는 타입&lt;/u&gt;과 &lt;u&gt;Null이 &lt;b&gt;허용되지 않는&lt;/b&gt; 타입&lt;/u&gt;이 구분되어 있습니다. 코드로 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val a: String = null // 컴파일 에러
var b: String = &quot;aabbcc&quot;
b = null // 컴파일 에러&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 변수 a와 b는 모두 null을 허용하지 않는 변수입니다. 그럼 null을 허용하는 변수는 어떻게 선언할 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1663848283995&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val a: String? = null
var b: String? = &quot;aabbcc&quot;
b = null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 물음표(?) 키워드가 많이 쓰입니다. 여기서의 물음표 키워드는 String 값이 null일수도 아닐수도 있다는 의미입니다. 즉, &lt;u&gt;null을 허용한다&lt;/u&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 class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;println(a.length)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a 변수의 문자열의 길이를 출력하는 구문입니다. 애석하지만 해당 코드는 실행할 수 없습니다. 엄밀히 말하면 &lt;b&gt;컴파일할 수 없습니다.&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 왜냐하면 변수 a는 이미 null을 허용하는 타입의 String 이기 때문입니다. 따라서 코틀린은 이에 대한 물음표 키워드를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;println(a?.length)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드는 a 변수가 null이면 null을 리턴하고 값이 있으면 length를 리턴합니다. 일종의 안전 연산자입니다.&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;b&gt;엘비스 연산자(?:)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 ? 표현식이 사용되는 구문은 엘비스 연산자라는 구문에도 사용됩니다. 엘비스 연산자는 아래와 같이 사용됩니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;val c = a?.length ?: 0&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;위 코드는 a?.length 값이 null이 리턴된다면 0을 출력하고 a?.length 값이 존재한다면 해당 값을 출력합니다. 자바에서의 삼항연산자와 유사한 문법입니다. 어쩌면 default 값을 정하기에 좋을 것 같아보입니다.&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;b&gt;코틀린에서 NPE를 조심해야하는 경우&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 코틀린에서 NPE가 발생하는 경우는 자바에서보다 적을 가능성이 높다고 합니다.&lt;u&gt;(가능성이 높습니다. 어플리케이션 기준으로는 같은 언어를 쓰더라도 개발자 역량에 따라 다릅니다.)&lt;/u&gt; &amp;nbsp;하지만 가능성이 자바가 더 높기 때문에 같은 JVM 메모리 시스템을 공유하는 JAVA와 Kotlin은 서로 공유될 수 있는데요.&lt;/p&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를 Jar화하여 kotlin 프로젝트에서 활용하는 경우입니다. 이 경우, 자바코드에서의 NPE는 자바코드를 어떻게 작성했는지에 따라서 동작이 달라지기 때문에 코틀린의 영역이 아니므로 컴파일러가 자바코드의 NPE검사를 진행할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 자바 클래스와 같이 사용하는 경우에는 NPE 발생 가능성에 대해서 고려하여 코드를 작성해야 합니다. (안전 연산자 사용 권장!!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <category>kotlin NPE</category>
      <category>Kotlin null safety</category>
      <category>코틀린 ?:</category>
      <category>코틀린 NullPointerException</category>
      <category>코틀린 삼항연산자</category>
      <category>코틀린 엘비스 연산자</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/468</guid>
      <comments>https://sas-study.tistory.com/468#entry468comment</comments>
      <pubDate>Thu, 22 Sep 2022 21:17:26 +0900</pubDate>
    </item>
    <item>
      <title>Java에서 Iterator를 List로 변경하는 방법</title>
      <link>https://sas-study.tistory.com/467</link>
      <description>&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;업무하는 도중에 JsonNode 인스턴스를 다루었는데 해당 json의 field name들을 List로 가져와야 하는 일이 있었습니다.&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;jsonNode.fieldNames()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드가 반환타입이 &lt;b&gt;Iterator&amp;lt;String&amp;gt;&lt;/b&gt; 이었고 이를 &lt;u&gt;List&amp;lt;String&amp;gt;&lt;/u&gt; 타입으로 변환이 필요하여 찾아보았고 업무를 종료하고 정리를 해두면 다른분들도 결정하실때 좋을 것 같아서 공유합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;JsonNode jsonNode = ...;
Iterator&amp;lt;String&amp;gt; fieldNameIterator = jsonNode.fieldNames();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 iterator 변수를 List 타입으로 바꾸어 보자!&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;b&gt;반복문(for, while 등)&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; fieldNameList = new ArrayList&amp;lt;&amp;gt;();
while(fieldNameIterator.hasNext()) {
   fieldNameList.add(fieldNameIterator.next());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단하고 쉽게 떠올릴만한 기본적인 방법. iterator를 돌면서 list를 채운다. 어찌보면 직접 구현하는 방법.&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;b&gt;JAVA 8 Iterator.forEachRemaining&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; fieldNameList = new ArrayList&amp;lt;&amp;gt;();
fieldNameIterator.forEachRemaining(fieldNameList::add);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8 혹은 그 이상의 버전에서는 iterator 타입의 forEachRemaining 메소드를 활용하면 List를 채울 수 있다. forEachRemaining 메소드의 참조로 add() 메소드를 전달하면 해당 iterator 인스턴스의 제네릭 타입의 value를 add하게 된다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;JAVA 8 Stream API&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Iterable&amp;lt;String&amp;gt; iterable = () -&amp;gt; fieldNameIterator;
List&amp;lt;String&amp;gt; fieldNameList = StreamSupport.stream(iterable.spliterator(), false)
      .collect(Collectors.toList());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 stream API를 활용하는 형태에서는 Iterator 타입을 &lt;u&gt;Iterable&lt;/u&gt; 타입으로 변경해야 합니다. StreamSupport 클래스를 활용하여 stream을 열고 list로 만드는 패턴을 활용합니다.&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;b&gt;Guava&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; fieldNames = ImmutableList.copyOf(fieldNameIterator); // ImmutableList를 활용하여 불변 List 생성.
List&amp;lt;String&amp;gt; fieldNames = Lists.newArrayList(fieldNameIterator); // Lists 클래스의 newArrayList를 활용하여 가변 List 생성.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ImmutableList 클래스를 활용하여 불변 List 객체를 만들 수 있고, Lists.newArrayList를 활용하여 가변 List 객체를 만들 수 있다.&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;b&gt;Apache Commons IteratorUtils Class&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; fieldNames = IteratorUtils.toList(fieldNameIterator);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IteratorUtils 클래스의 toList를 활용할 수 있다. 이 방법이 제가 선택한 방법인데 가장 심플하고 깔끔하게 정리할 수 있는 코드라서 선택하게 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 단순한 컨버팅하는 부분은 지저분하게 비즈니스 코드와 엮일 필요가 없이 별도의 static 함수로 만들거나 라이브러리의 도움을 받는 것이 제일 깔끔한 코드를 작성하는 방법이라고 생각합니다. 따라서 구현을 직접하시더라도 IteratorUtils 클래스를 직접 만들어서 마치 라이브러리의 도움을 받는 코드인 척하거나 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;감사합니다.&lt;/p&gt;</description>
      <category>프로그래밍 언어/Java</category>
      <category>iterator List로 변경</category>
      <category>Iterator to List</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/467</guid>
      <comments>https://sas-study.tistory.com/467#entry467comment</comments>
      <pubDate>Thu, 22 Sep 2022 17:52:20 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] if문 / for, while 문 / when 문</title>
      <link>https://sas-study.tistory.com/466</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;if 문&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;가장 기본적인 형태&lt;/u&gt;의 if 문&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;val job = &quot;Backend Developer&quot;

if (job == &quot;Backend Developer&quot;) {
    println(&quot;백엔드 개발자&quot;)
} else if (job == &quot;Frontend Developer&quot;) {
    println(&quot;프론트엔드 개발자&quot;)
} else {
    println(&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;if문은 &lt;u&gt;&lt;b&gt;표현식&lt;/b&gt;&lt;/u&gt;이다. 따라서 Java에서와는 다르게 동작한다. 대입이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;val age : Int = 10

val str = if (age &amp;gt; 10) {
    &quot;성인&quot;
} else {
    &quot;아이&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 str = &quot;성인&quot;, str = &quot;아이&quot; 처럼 if문 안에 대입문을 넣어주었어야 할텐데 코틀린에서는 표현식으로 동작하기 때문에 위와 같은 대입식이 가능하다.&lt;/p&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;u&gt;코틀린은 삼항연산자가 없다&lt;/u&gt;. 하지만 if else가 표현식처럼 동작하기 때문에 불필요해서 없다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val num1 = 1
val num2 = 2
val result = if (num2 &amp;gt; num1) num2 else num1 // 해당 코드가 삼항연산자처럼 동작한다.&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;for 문&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;범위 연산자&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;for (i in 0 .. 3) {
    println(i)
} // 0 1 2 3&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;u&gt;&lt;b&gt;until&lt;/b&gt; 연산자&lt;/u&gt; : until 뒤에 오는 숫자는 포함하지 않는다.(미만)&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;for (i in 0 until 3) {
    println(i)
} // 0 1 2&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;u&gt;&lt;b&gt;step&lt;/b&gt; 연산자&lt;/u&gt; : 값을 증가시킨다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;for (i in 0 .. 6 step 2) {
    println(i)
} // 0 2 4 6&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;u&gt;&lt;b&gt;downTo&lt;/b&gt; 연산자&lt;/u&gt; : 값을 감소시킨다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;for (i in 3 downTo 1) {
    println(i)
} // 3 2 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;u&gt;Array 반복&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val numbers = arrayOf(1,2,3)
for(i in numbers) {
    println(i)
} // 1 2 3&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;while 문&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서의 while문과 동일하다. 조건을 확인한 후 true 면 코드를 실행하고 false 면 코드를 실행하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;var x = 5
while (x &amp;gt; 0) {
    println(x)
    x--
} // 5 4 3 2 1&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;when 문&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 &lt;u&gt;switch case&lt;/u&gt;를 활용했을 때와 비슷한 동작을 한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val value = 2

val result = when (value) {
    1 -&amp;gt; &quot;첫번쨰&quot;
    2 -&amp;gt; &quot;두번쨰&quot;
    3 -&amp;gt; &quot;세번쨰&quot;
    4 -&amp;gt; &quot;네번째&quot;
    else -&amp;gt; &quot;기타&quot;
}
println(result) // &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;만약 아래와 같은 enum 클래스가 있다면!&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;enum class Developer {
    BACKEND, FRONTEND
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;when(getDeveloper()) {
    Developer.BACKEND -&amp;gt; println(&quot;backend&quot;)
    Developer.FRONTEND -&amp;gt; println(&quot;frontend&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;when 절을 활용할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;u&gt;&lt;b&gt;else&lt;/b&gt;를 생략&lt;/u&gt;할 수 있습니다. 왜냐하면 Developer enum 클래스에는 BACKEND, FRONTEND 값만 존재하기 때문에 코틀린 컴파일러가 이에 대해서 추론할 수 있도록 제공합니다.&lt;/p&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;u&gt;NATIVE&lt;/u&gt;라는 타입을 추가한다면..?&lt;/p&gt;
&lt;pre id=&quot;code_1663745729384&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum class Developer {
    BACKEND, FRONTEND, NATIVE
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Absdl/btrMIXBXSRf/YfAsobka6EpmPGmgwhdlhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Absdl/btrMIXBXSRf/YfAsobka6EpmPGmgwhdlhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Absdl/btrMIXBXSRf/YfAsobka6EpmPGmgwhdlhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAbsdl%2FbtrMIXBXSRf%2FYfAsobka6EpmPGmgwhdlhK%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;674&quot; height=&quot;91&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;91&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;when 절에서 &lt;u&gt;&lt;b&gt;컴파일 에러&lt;/b&gt;&lt;/u&gt;가 발생합니다. NATIVE 케이스를 추가하거나 else를 두라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 코틀린에서의 when 절은 여러개의 조건을 콤마(,)로 구분해서 한줄에 정의할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    when(getNumber()) {
    	1, 2 -&amp;gt; println(&quot;첫번째 혹은 두번째&quot;)
        else -&amp;gt; println(&quot;그 외.&quot;)
    }
}

fun getNumber() = 2&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;&lt;a href=&quot;https://fastcampus.co.kr/courses/211160&quot;&gt;https://fastcampus.co.kr/courses/211160&lt;/a&gt;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <category>kotlin for문</category>
      <category>kotlin if 문</category>
      <category>kotlin when 구문</category>
      <category>kotlin while 구문</category>
      <category>코틀린 if 문</category>
      <category>코틀린 switch 문</category>
      <category>코틀린 when 문</category>
      <category>코틀린 while 문</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/466</guid>
      <comments>https://sas-study.tistory.com/466#entry466comment</comments>
      <pubDate>Wed, 21 Sep 2022 16:38:48 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린 기초] Function 함수 예제 모음.</title>
      <link>https://sas-study.tistory.com/465</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;default Parameter나 named argument를 봤을 때는 자바를 사용하는 실무에서 마주칠법한 오류에 많은 도움이 될 것 같아보였다. 다른 내용들은 기본적인 내용이므로 코틀린을 많이 작성하다 보면 잘 익어갈만한 내용으로 보인다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;b&gt;기본적인 함수&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1663716267740&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun sum(num1 : Int, num2 : Int) : Int {
    return num1 + num2
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;표현식 스타일 함수&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1663716359255&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun sum2(num1 : Int, num2 : Int) : Int = num1 + num2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;표현식과 반환 타입을 생략&lt;/u&gt; 가능한 함수&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1663716375210&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun sum3(num1 : Int, num2 : Int) = num1 + num2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;s&gt;(단계가 어째 자바 람다식 축약하는 것 같다...)&lt;/s&gt;&lt;/i&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;u&gt;반환 타입을 제거하면 컴파일 오류가 발생&lt;/u&gt;한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// 몸통이 있는 함수는 반환 타입을 제거하면 컴파일 오류
fun sum4(num1 : Int, num2 : Int) : Int /* 이거 제거하면 안됨. */ {
    return num1 + num2
}&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;코틀린에서는 void라는 키워드가 없고 Unit 이라는 키워드가 있다. 아래의 함수는 자바로 치면 void 코틀린 Unit을 반환하는 함수이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// 반환 타입이 없는 함수는 Unit을 반환한다. (Void)
fun printSum(num1 : Int, num2 : Int) {
    println(&quot;$num1 + $num2 = ${num1 + num2}&quot;)
}&lt;/code&gt;&lt;/pre&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;b&gt;Default Parameter&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun greeting(message: String = &quot;디폴트 파라미터 셋팅!!&quot;) {
    println(message)
}

fun main() {
    greeting()
    greeting(&quot;다른 파라미터 전달!!!&quot;)
}
// 디폴트 파라미터 셋팅!!
// 다른 파라미터 전달!!!&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;&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;b&gt;Named Argument&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun log(level: String = &quot;INFO&quot;, message: String) {
    println(&quot;[$level] $message&quot;)
}

fun main() {
    log(message = &quot;인포 로그&quot;)
    log(level = &quot;DEBUG&quot;, &quot;디버그 로그&quot;)
    log(&quot;WARN&quot;, &quot;워닝 로그&quot;)
    log(level = &quot;ERROR&quot;, message = &quot;에러 로그&quot;)
}
// [INFO] 인포 로그
// [DEBUG] 디버그 로그
// [WARN] 워닝 로그
// [ERROR] 에러 로그&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디폴트 파라미터와 혼용된 모습인데. 우선 log라는 함수를 호출할 때, 함수의 파라미터에 값을 대입하듯 대입식을 argument로 전달할 수 있다는 모습으로 보인다. 여기서 대입식을 전달할때 함수에 쓰여진 파라미터 순서는 대입식에는 영향을 끼치지 못한다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;message = &quot;에러로그&quot; 로 전달된 named argument는 message 파라미터 변수에 할당될 뿐이지 level 변수에 영향을 끼치지는 않는다는 것이다.&lt;/p&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_1663716925983&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun log(level: String = &quot;INFO&quot;, message: String) {
    println(&quot;[$level] $message&quot;)
}

fun main() {
    log(&quot;디버그 로그&quot;, &quot;DEBUG&quot;)
    log(message = &quot;디버그 로그&quot;, level = &quot;DEBUG&quot;)
}
// [디버그 로그] DEBUG
// [DEBUG] 디버그 로그&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 두 함수 호출 결과는 정반대로 발생한다. 전달하는 argument의 순서는 동일한것 같으나 첫번째 호출에서는 둘다 String 값이다보니 어느 변수에 정확히 할당되는지 개발자가 런타임을 확인한 후에 알 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 호출은 named argument를 활용하여 전달하였기 때문에 argument는 parameter에 값이 할당될 때 parameter 변수명을 보고 개발자가 의도한 대로 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;&lt;a href=&quot;https://fastcampus.co.kr/courses/211160&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://fastcampus.co.kr/courses/211160&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1663716136491&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;성인 교육 서비스 기업, 패스트캠퍼스는 개인과 조직의 실질적인 '업(業)'의 성장을 돕고자 모든 종류의 교육 콘텐츠 서비스를 제공하는 대한민국 No. 1 교육 서비스 회사입니다.&quot; data-og-host=&quot;fastcampus.co.kr&quot; data-og-source-url=&quot;https://fastcampus.co.kr/courses/211160&quot; data-og-url=&quot;https://fastcampus.co.kr/courses/211160&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/en9Por/hyPQ3kY9CO/t2Yj4YMr0ux4SzmMVvf4uk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://fastcampus.co.kr/courses/211160&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://fastcampus.co.kr/courses/211160&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/en9Por/hyPQ3kY9CO/t2Yj4YMr0ux4SzmMVvf4uk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;성인 교육 서비스 기업, 패스트캠퍼스는 개인과 조직의 실질적인 '업(業)'의 성장을 돕고자 모든 종류의 교육 콘텐츠 서비스를 제공하는 대한민국 No. 1 교육 서비스 회사입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;fastcampus.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>프로그래밍 언어/Kotlin</category>
      <category>argument parameter</category>
      <category>default parameter</category>
      <category>named argument</category>
      <category>코틀린 named parameter</category>
      <category>코틀린 기초</category>
      <category>코틀린 함수 작성</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/465</guid>
      <comments>https://sas-study.tistory.com/465#entry465comment</comments>
      <pubDate>Wed, 21 Sep 2022 08:39:10 +0900</pubDate>
    </item>
    <item>
      <title>[번역글] MSA 서비스별 데이터베이스 패턴(Database per service pattern)</title>
      <link>https://sas-study.tistory.com/464</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;해당 포스팅은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://microservices.io/patterns/data/database-per-service.html&quot;&gt;해당 게시글&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;을 번역한 포스팅임을 명시합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;서론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스&lt;span&gt; &lt;/span&gt;아키텍처&lt;span&gt; &lt;/span&gt;패턴을&lt;span&gt; &lt;/span&gt;사용하여&lt;span&gt; &lt;/span&gt;온라인&lt;span&gt; &lt;/span&gt;스토어&lt;span&gt; &lt;/span&gt;애플리케이션을&lt;span&gt; &lt;/span&gt;개발한다고&lt;span&gt; &lt;/span&gt;가정해&lt;span&gt; &lt;/span&gt;봅시다&lt;span&gt;. &lt;/span&gt;대부분의&lt;span&gt; &lt;/span&gt;서비스들은&lt;span&gt; &lt;/span&gt;하나의&lt;span&gt; &lt;/span&gt;종류의&lt;span&gt; &lt;/span&gt;데이터베이스에&lt;span&gt; &lt;/span&gt;데이터를&lt;span&gt; &lt;/span&gt;저장해야&lt;span&gt; &lt;/span&gt;합니다&lt;span&gt;. &lt;/span&gt;예를&lt;span&gt; &lt;/span&gt;들어&lt;span&gt;, Order Service&lt;/span&gt;는&lt;span&gt; Order&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;데이터를&lt;span&gt; &lt;/span&gt;저장하고&lt;span&gt; Customer Service&lt;/span&gt;는&lt;span&gt; Customer&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;데이터를&lt;span&gt; &lt;/span&gt;저장합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;customersandorders.png&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgW7kQ/btrLu8rHyW2/kDv08ZpmCgUVuCrn2VOcnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgW7kQ/btrLu8rHyW2/kDv08ZpmCgUVuCrn2VOcnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgW7kQ/btrLu8rHyW2/kDv08ZpmCgUVuCrn2VOcnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgW7kQ%2FbtrLu8rHyW2%2FkDv08ZpmCgUVuCrn2VOcnk%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;1148&quot; height=&quot;598&quot; data-filename=&quot;customersandorders.png&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;b&gt;Problem&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스&lt;span&gt; &lt;/span&gt;애플리케이션의&lt;span&gt; &lt;/span&gt;데이터베이스&lt;span&gt; &lt;/span&gt;아키텍처는&lt;span&gt; &lt;/span&gt;무엇입니까&lt;span&gt;?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;b&gt;Forces&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스는&amp;nbsp;&lt;b&gt;독립적&lt;/b&gt;으로 개발, 배포 및 확장될 수 있도록&amp;nbsp;&lt;u&gt;&lt;span&gt;느슨한 결합&lt;/span&gt;&lt;/u&gt;이 필요합니다.&lt;/li&gt;
&lt;li&gt;일부 비즈니스 트랜잭션은 여러 서비스에 걸쳐 처리되도록 불변성을 적용해야 합니다. 예를 들어, Place Order 유스케이스에서는 새로운 주문이 발생할 때, 고객의 신용 한도를 초과하지 않는지 확인해야 합니다. 다른 비즈니스 트랜잭션은 여러 서비스가 소유한 데이터를 업데이트 해야 합니다.&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;span&gt;&amp;nbsp;&lt;/span&gt;데이터&lt;span&gt;&amp;nbsp;&lt;/span&gt;저장&lt;span&gt;&amp;nbsp;&lt;/span&gt;요구사항이&lt;span&gt;&amp;nbsp;&lt;/span&gt;다를&lt;span&gt;&amp;nbsp;&lt;/span&gt;수&lt;span&gt;&amp;nbsp;&lt;/span&gt;있습니다&lt;span&gt;.&amp;nbsp;&lt;/span&gt;어떤&lt;span&gt;&amp;nbsp;&lt;/span&gt;서비스는&lt;span&gt;&amp;nbsp;&lt;/span&gt;관계형&lt;span&gt;&amp;nbsp;&lt;/span&gt;데이터베이스가&lt;span&gt;&amp;nbsp;&lt;/span&gt;최선의&lt;span&gt;&amp;nbsp;&lt;/span&gt;선택일&lt;span&gt;&amp;nbsp;&lt;/span&gt;수&lt;span&gt;&amp;nbsp;&lt;/span&gt;있고&lt;span&gt;,&amp;nbsp;&lt;/span&gt;다른&lt;span&gt;&amp;nbsp;&lt;/span&gt;서비스에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;복잡하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;구조화되지&lt;span&gt;&amp;nbsp;&lt;/span&gt;않은&lt;span&gt;&amp;nbsp;&lt;/span&gt;데이터를&lt;span&gt;&amp;nbsp;&lt;/span&gt;저장하는데&lt;span&gt;&amp;nbsp;&lt;/span&gt;능숙한&lt;span&gt;&amp;nbsp;MongoDB&amp;nbsp;&lt;/span&gt;혹은&lt;span&gt;&amp;nbsp;&lt;/span&gt;그래프&lt;span&gt;&amp;nbsp;&lt;/span&gt;데이터를&lt;span&gt;&amp;nbsp;&lt;/span&gt;효율적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;저장하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;쿼리하도록&lt;span&gt;&amp;nbsp;&lt;/span&gt;설계된&lt;span&gt;&amp;nbsp;Neo4J&lt;/span&gt;같은&lt;span&gt;&amp;nbsp;NoSQL&amp;nbsp;&lt;/span&gt;데이터베이스가&lt;span&gt;&amp;nbsp;&lt;/span&gt;필요할&lt;span&gt;&amp;nbsp;&lt;/span&gt;수&lt;span&gt;&amp;nbsp;&lt;/span&gt;있습니다&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Solution&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 마이크로 서비스의 영구 데이터를 해당 서비스에 대해 private하게 유지하고 해당 API를 통해서만 엑세스할 수 있습니다. 서비스의 트랜잭션에는 해당 데이터베이스만 포함됩니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의&lt;span&gt; &lt;/span&gt;다이어그램은&lt;span&gt; &lt;/span&gt;이&lt;span&gt; &lt;/span&gt;패턴의&lt;span&gt; &lt;/span&gt;구조를&lt;span&gt; &lt;/span&gt;보여줍니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;689&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhcUL0/btrLu9EeTKk/OsV0mKabjfvCjA3ZhBM7gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhcUL0/btrLu9EeTKk/OsV0mKabjfvCjA3ZhBM7gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhcUL0/btrLu9EeTKk/OsV0mKabjfvCjA3ZhBM7gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhcUL0%2FbtrLu9EeTKk%2FOsV0mKabjfvCjA3ZhBM7gK%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;1203&quot; height=&quot;689&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;689&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 데이터베이스는 사실상 해당 서비스 구현의 일부입니다. 다른 서비스에서 직접 엑세스할 수 없습니다. 서비스의 영구 데이터를 private 하게 유지하는 몇가지 방법들이 있습니다. 각 서비스에 대해서 데이터베이스 서버를 프로비저닝할 필요가 없습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 관계형 디비를 사용하는 경우 아래와 같은 선택사항이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 별 private 테이블 : 각 서비스는 해당 서비스에서만 엑세스해야 하는 테이블 집합을 가집니다.&lt;/li&gt;
&lt;li&gt;서비스 별 스키마 : 각 서비스에는 해당 서비스에 대한 전용 데이터베이스 스키마가 있습니다.&lt;/li&gt;
&lt;li&gt;서비스 당 데이터베이스 서버 : 각 서비스에는 자체 데이터베이스 서버가 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;서비스 별 private 테이블&lt;/u&gt; 및 &lt;u&gt;서비스 별 스키마&lt;/u&gt;는 오버헤드가 낮은 편에 속합니다. &lt;u&gt;서비스별 스키마&lt;/u&gt;를 사용하면 소유권이 더 명확해지기 때문에 매력적일 수 있습니다. &lt;u&gt;일부의 높은 트래픽이 쏠리는 서비스에는 &lt;b&gt;자체 데이터베이스 서버&lt;/b&gt;가 필요&lt;/u&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;span&gt; &lt;/span&gt;강해질수록&lt;span&gt; &lt;/span&gt;좋습니다&lt;span&gt;. &lt;/span&gt;예를&lt;span&gt; &lt;/span&gt;들어&lt;span&gt;, &lt;/span&gt;각&lt;span&gt; &lt;/span&gt;서비스에&lt;span&gt; &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;데이터베이스&lt;span&gt; &lt;/span&gt;사용자&lt;span&gt; ID&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;할당하고&lt;span&gt; &lt;/span&gt;권한&lt;span&gt; &lt;/span&gt;부여와&lt;span&gt; &lt;/span&gt;같은&lt;span&gt; &lt;/span&gt;데이터베이스&lt;span&gt; &lt;/span&gt;엑세스&lt;span&gt; &lt;/span&gt;제어&lt;span&gt; &lt;/span&gt;매커니즘을&lt;span&gt; &lt;/span&gt;활용할&lt;span&gt; &lt;/span&gt;수도&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;. &lt;/span&gt;캡슐화를&lt;span&gt; &lt;/span&gt;하기&lt;span&gt; &lt;/span&gt;위한&lt;span&gt; &lt;/span&gt;별도의&lt;span&gt; &lt;/span&gt;장벽이&lt;span&gt; &lt;/span&gt;없다면&lt;span&gt; &lt;/span&gt;개발자는&lt;span&gt; &lt;/span&gt;항상&lt;span&gt; &lt;/span&gt;서비스의&lt;span&gt; API&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;우회하고&lt;span&gt; &lt;/span&gt;해당&lt;span&gt; &lt;/span&gt;데이터에&lt;span&gt; &lt;/span&gt;직접&lt;span&gt; &lt;/span&gt;엑세스하려고&lt;span&gt; &lt;/span&gt;들&lt;span&gt; &lt;/span&gt;것&lt;span&gt; &lt;/span&gt;입니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Example&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chrisrichardson.net/post/microservices/patterns/data/2019/07/15/ftgo-database-per-service.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;FTGO &lt;/span&gt;애플리케이션&lt;/a&gt;은&lt;span&gt; &lt;/span&gt;이&lt;span&gt; &lt;/span&gt;접근&lt;span&gt; &lt;/span&gt;방식을&lt;span&gt; &lt;/span&gt;사용하는&lt;span&gt; &lt;/span&gt;애플리케이션의&lt;span&gt; &lt;/span&gt;예입니다&lt;span&gt;. &lt;/span&gt;각&lt;span&gt; &lt;/span&gt;서비스에는&lt;span&gt; &lt;/span&gt;공유&lt;span&gt; MySQL &lt;/span&gt;서버의&lt;span&gt; &lt;/span&gt;자체&lt;span&gt;(&lt;/span&gt;논리&lt;span&gt;) &lt;/span&gt;데이터베이스에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;액세스&lt;span&gt; &lt;/span&gt;권한만&lt;span&gt; &lt;/span&gt;부여하는&lt;span&gt; &lt;/span&gt;데이터베이스&lt;span&gt; &lt;/span&gt;자격&lt;span&gt; &lt;/span&gt;증명이&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;. &lt;/span&gt;자세한&lt;span&gt; &lt;/span&gt;내용은&lt;span&gt; &lt;/span&gt;이&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://chrisrichardson.net/post/microservices/patterns/data/2019/07/15/ftgo-database-per-service.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;블로그&lt;/a&gt;&lt;span&gt; &lt;/span&gt;게시물을&lt;span&gt; &lt;/span&gt;참조하세요&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Resulting context&lt;/b&gt;&lt;/h4&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;각 서비스는 요구 사항에 가장 적합한 데이터베이스를 선택해서 사용할 수 있습니다. 예를 들어, 텍스트 검색을 수행하는 서비스는 ElasticSearch를 사용할 수 있습니다. 소셜 그래프를 조작하는 서비스는 Neo4J를 사용할 수 있습니다.&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;서비스별로 데이터베이스를 사용하면 다음과 같은 단점이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 서비스에 걸쳐 있는 비즈니스 트랜잭션을 구현하려는 것은 간단하지 않습니다. 분산 트랜잭션은 CAP 이론 때문에 피하는 것이 가장 좋습니다. (&lt;a href=&quot;https://sabarada.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CAP 이론&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;현재 여러 데이터베이스에 있는 데이터를 결합하는 쿼리를 구현하는 것은 어렵습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;여러 SQL 및 NoSQL 데이터베이스 관리의 복잡성&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;여러 서비스에 걸친 트랜잭션 및 쿼리를 구현하기 위한 다양한 패턴이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스에 걸친 트랜잭션 구현 : &lt;u&gt;SAGA 패턴&lt;/u&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;b&gt;API Composition&lt;/b&gt; : &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;응용프로그램은&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;데이터베이스가&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;아닌&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;조인을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;수행합니다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;예를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;들어&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;서비스&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;또는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; API &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;게이트웨이&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;먼저&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; Customer Service&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;에서&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;고객을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;검색한&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;다음&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; Order Service&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;쿼리하여&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;고객의&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;가장&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;최신&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;주문을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;반환해주므로써&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;고객과&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;주문을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;함께&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;검색할&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;수&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;있습니다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;u&gt;Command Query Responsibility Segregation(CQRS)&lt;/u&gt;&lt;/b&gt; : &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;여러&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;서비스의&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;데이터를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;포함하는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하나&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이상의&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구체적인&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;뷰&lt;/b&gt;를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;유지관리&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;합니다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;뷰는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;각&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;서비스가&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;데이터를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;업데이트할&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;때&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;발생하는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;u&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이벤트를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구독하는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;서비스에&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;의해&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;/u&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;u&gt;유지&lt;/u&gt;됩니다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;예를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;들어&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;온라인&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;상점은&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; Customer&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;와&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; Order&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;결합하는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;뷰를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;유지함으로써&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;특정&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;지역의&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;고객과&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;최신&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;주문을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;찾는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;쿼리를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구현할&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;수&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;있습니다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;뷰는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; Customer &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;및&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; Order &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이벤트를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구독하는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;서비스에&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;의해서&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;업데이트&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;됩니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;</description>
      <category>흑구의 공부내용 공유</category>
      <category>msa architecture</category>
      <category>MSA 마이크로서비스 데이터베이스</category>
      <category>msa 서비스 별 데이터베이스</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/464</guid>
      <comments>https://sas-study.tistory.com/464#entry464comment</comments>
      <pubDate>Tue, 6 Sep 2022 13:26:18 +0900</pubDate>
    </item>
    <item>
      <title>[번역글] MSA 공유 데이터베이스 패턴 (Shared database)</title>
      <link>https://sas-study.tistory.com/463</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;해당 포스팅은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://microservices.io/patterns/data/shared-database.html&quot;&gt;해당 게시글&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;을 번역한 포스팅임을 명시합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;서론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스&lt;span&gt; &lt;/span&gt;아키텍쳐&lt;span&gt; &lt;/span&gt;패턴을&lt;span&gt; &lt;/span&gt;활용하여&lt;span&gt; &lt;/span&gt;온라인&lt;span&gt; &lt;/span&gt;스토어&lt;span&gt; &lt;/span&gt;애플리케이션을&lt;span&gt; &lt;/span&gt;개발한다고&lt;span&gt; &lt;/span&gt;가정해&lt;span&gt; &lt;/span&gt;봅시다&lt;span&gt;. &lt;/span&gt;대부분의&lt;span&gt; &lt;/span&gt;마이크로서비스에서는&lt;span&gt; &lt;/span&gt;일종의&lt;span&gt; &lt;/span&gt;데이터베이스에&lt;span&gt; &lt;/span&gt;데이터를&lt;span&gt; &lt;/span&gt;저장합니다&lt;span&gt;. &lt;/span&gt;예를&lt;span&gt; &lt;/span&gt;들어&lt;span&gt; &lt;b&gt;Order Service&lt;/b&gt;&lt;/span&gt;는&lt;span&gt; &lt;/span&gt;주문&lt;span&gt;(Order)&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;정보를&lt;span&gt; &lt;/span&gt;저장하고&lt;span&gt; &lt;b&gt;Customer Service&lt;/b&gt;&lt;/span&gt;는&lt;span&gt; &lt;/span&gt;고객&lt;span&gt;(Customer)&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;정보를&lt;span&gt; &lt;/span&gt;저장합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;customersandorders.png&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uEGeX/btrLuC0J6Rn/CNOnnKNmUe0vPcPve3sIk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uEGeX/btrLuC0J6Rn/CNOnnKNmUe0vPcPve3sIk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uEGeX/btrLuC0J6Rn/CNOnnKNmUe0vPcPve3sIk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuEGeX%2FbtrLuC0J6Rn%2FCNOnnKNmUe0vPcPve3sIk1%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;1148&quot; height=&quot;598&quot; data-filename=&quot;customersandorders.png&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;598&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;b&gt;Problem&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스&lt;span&gt; &lt;/span&gt;애플리케이션의&lt;span&gt; &lt;/span&gt;데이터베이스&lt;span&gt; &lt;/span&gt;아키텍처는&lt;span&gt; &lt;/span&gt;무엇인가&lt;span&gt;??&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Forces&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스는 &lt;b&gt;독립적&lt;/b&gt;으로 개발, 배포 및 확장될 수 있도록 &lt;u&gt;느슨한 결합&lt;/u&gt;이 필요합니다.&lt;/li&gt;
&lt;li&gt;일부 비즈니스 트랜잭션은 여러 서비스에 걸쳐 처리되도록 불변성을 적용해야 합니다. 예를 들어, Place Order 유스케이스에서는 새로운 주문이 발생할 때, 고객의 신용 한도를 초과하지 않는지 확인해야 합니다. 다른 비즈니스 트랜잭션은 여러 서비스가 소유한 데이터를 업데이트 해야 합니다.&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;span&gt; &lt;/span&gt;데이터&lt;span&gt; &lt;/span&gt;저장&lt;span&gt; &lt;/span&gt;요구사항이&lt;span&gt; &lt;/span&gt;다를&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;. &lt;/span&gt;어떤&lt;span&gt; &lt;/span&gt;서비스는&lt;span&gt; &lt;/span&gt;관계형&lt;span&gt; &lt;/span&gt;데이터베이스가&lt;span&gt; &lt;/span&gt;최선의&lt;span&gt; &lt;/span&gt;선택일&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있고&lt;span&gt;, &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;서비스에는&lt;span&gt; &lt;/span&gt;복잡하고&lt;span&gt; &lt;/span&gt;구조화되지&lt;span&gt; &lt;/span&gt;않은&lt;span&gt; &lt;/span&gt;데이터를&lt;span&gt; &lt;/span&gt;저장하는데&lt;span&gt; &lt;/span&gt;능숙한&lt;span&gt; MongoDB &lt;/span&gt;혹은&lt;span&gt; &lt;/span&gt;그래프&lt;span&gt; &lt;/span&gt;데이터를&lt;span&gt; &lt;/span&gt;효율적으로&lt;span&gt; &lt;/span&gt;저장하고&lt;span&gt; &lt;/span&gt;쿼리하도록&lt;span&gt; &lt;/span&gt;설계된&lt;span&gt; Neo4J&lt;/span&gt;같은&lt;span&gt; NoSQL &lt;/span&gt;데이터베이스가&lt;span&gt; &lt;/span&gt;필요할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Solutions&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러&lt;span&gt; &lt;/span&gt;서비스에서&lt;span&gt; &lt;/span&gt;공유하는&lt;span&gt; (&lt;/span&gt;단일&lt;span&gt;) &lt;/span&gt;데이터베이스를&lt;span&gt; &lt;/span&gt;사용합니다&lt;span&gt;. &lt;/span&gt;각&lt;span&gt; &lt;/span&gt;서비스는&lt;span&gt; &lt;/span&gt;로컬&lt;span&gt; ACID &lt;/span&gt;트랜잭션을&lt;span&gt; &lt;/span&gt;사용하여&lt;span&gt; &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;서비스가&lt;span&gt; &lt;/span&gt;소유한&lt;span&gt; &lt;/span&gt;데이터에&lt;span&gt; &lt;/span&gt;자유롭게&lt;span&gt; &lt;/span&gt;엑세스&lt;span&gt; &lt;/span&gt;합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OrderService와 CustomerService는 서로의 테이블에 자유롭게 엑세스합니다. 예를 들어, OrderService는 다음 ACID 트랜잭션을 사용하여 새로운 주문이 고객의 신용한도를 넘지 않도록 제한할 수 있습니다.&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: 100%;&quot;&gt;&lt;span&gt;BEGIN TRANSACTION&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;hellip;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;SELECT&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; ORDER_TOTAL&lt;/span&gt;&lt;br /&gt;&lt;span&gt;FROM ORDERS&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;WHERE CUSTOMER_ID = ?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;hellip;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;SELECT&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; CREDIT_LIMIT&lt;/span&gt;&lt;br /&gt;&lt;span&gt;FROM CUSTOMERS&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;WHERE CUSTOMER_ID = ?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;hellip;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;INSERT INTO ORDERS &amp;hellip;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;hellip;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;COMMIT TRANSACTION&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스는&lt;span&gt; &lt;/span&gt;동시&lt;span&gt; &lt;/span&gt;트랜잭션이&lt;span&gt; &lt;/span&gt;동일한&lt;span&gt; &lt;/span&gt;고객에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;주문을&lt;span&gt; &lt;/span&gt;생성하려고&lt;span&gt; &lt;/span&gt;할&lt;span&gt; &lt;/span&gt;때에도&lt;span&gt; &lt;/span&gt;신용한도를&lt;span&gt; &lt;/span&gt;초과하지&lt;span&gt; &lt;/span&gt;않도록&lt;span&gt; &lt;/span&gt;보장합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Resulting context&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴의 &lt;u&gt;&lt;b&gt;이점&lt;/b&gt;&lt;/u&gt;은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자는 친숙하고 간단한 ACID 트랜잭션을 활용하여 데이터 일관성을 적용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;단일 데이터베이스는 운영하기 더 간단합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴의 &lt;u&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/u&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;u&gt;개발시간 커플링&lt;/u&gt; : 예를 들어, OrderService에서 작업하는 개발자는 동일한 테이블에 엑세스하는 다른 서비스의 개발자와 스키마에 대한 신경을 함께 써야합니다. 이러한 커플링과 추가 조정은 개발생산성을 낮출 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;런타임 커플링&lt;/u&gt; : 모든 서비스가 동일한 데이터베이스에 엑세스하기 때문에 잠재적으로 서로 간섭할 수 있습니다. 예를 들어, 러닝타임이 긴 CustomerService의 트랜잭션이 Order 테이블에 대한 lock을 보유하고 있으면 OrderService의 트랜잭션의 Order 테이블에 대한 접근이 차단됩니다.&lt;/li&gt;
&lt;li&gt;단일&lt;span&gt; &lt;/span&gt;데이터베이스는&lt;span&gt; &lt;/span&gt;모든&lt;span&gt; &lt;/span&gt;서비스의&lt;span&gt; &lt;/span&gt;데이터&lt;span&gt; &lt;/span&gt;저장&lt;span&gt; &lt;/span&gt;및&lt;span&gt; &lt;/span&gt;엑세스&lt;span&gt; &lt;/span&gt;요구사항을&lt;span&gt; &lt;/span&gt;충족하지&lt;span&gt; &lt;/span&gt;않을&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>흑구의 공부내용 공유</category>
      <category>msa database 전략</category>
      <category>msa shared database</category>
      <category>msa 공유 데이터베이스 전략</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/463</guid>
      <comments>https://sas-study.tistory.com/463#entry463comment</comments>
      <pubDate>Tue, 6 Sep 2022 11:49:02 +0900</pubDate>
    </item>
    <item>
      <title>자바에서 병렬 스트림을 사용하는 경우</title>
      <link>https://sas-study.tistory.com/461</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 포스팅은 &lt;a href=&quot;https://www.baeldung.com/java-when-to-use-parallel-stream&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8 에서는 컬렉션을 데이터 스트림으로 쉽게 반복할 수 있는 Stream API를 도입했습니다. 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;해당 포스팅에서는 순차 스트림과 병렬 스트림의 차이점에 대해서 살펴보겠습니다. 먼저 병렬 스트림에서 사용하는 default fork-join pool을 살펴보겠습니다.&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;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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;자바에서의 Stream&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 Stream은 단순히 데이터 소스를 감싸는 Wrapper이므로 편리한 방식으로 데이터에 대한 대량 작업을 수행할 수 있습니다. 데이터를 저장하거나 기본 데이터 소스를 변경하지 않고 데이터 파이프라인에서 함수형 처리를 지원합니다.&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;b&gt;순차 스트림&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 기본적으로, 명시적으로 병렬로 지정되지 않는 한 자바의 모든 Stream 작업은 순차적으로 처리됩니다. 순차스트림은 단일 스레드로 처리됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662122315732&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; listOfNumbers = Arrays.asList(1, 2, 3, 4);
listOfNumbers.stream().forEach(number -&amp;gt;
    System.out.println(number + &quot; &quot; + Thread.currentThread().getName())
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드의 출력결과는 쉽게 예측할 수 있습니다. 리스트의 요소는 항상 순서대로 출력됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;실행결과 :&lt;br /&gt;1 main&lt;br /&gt;2 main&lt;br /&gt;3 main&lt;br /&gt;4 main&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;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; listOfNumbers = Arrays.asList(1, 2, 3, 4);
listOfNumbers.parallelStream().forEach(number -&amp;gt;
        System.out.println(number + &quot; &quot; + Thread.currentThread().getName())
);&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;4 ForkJoinPool.commonPool-worker-3 &lt;br /&gt;2 ForkJoinPool.commonPool-worker-5 &lt;br /&gt;1 ForkJoinPool.commonPool-worker-7 &lt;br /&gt;3 main&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Fork-Join Framework&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 스트림은 Fork-Join 프레임워크와 common pool of worker 스레드를 사용합니다. fork-join 프레임워크는 여러 스레드간의 작업 관리를 처리하기 위해 Java7의 java.util.concurrent에 추가되었습니다.&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;b&gt;소스 분할&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork-join 프레임워크는 &lt;b&gt;&lt;u&gt;worker 스레드간에 소스 데이터를 분할하고 작업 완료시 콜백을 처리하는 역할&lt;/u&gt;&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;우리는 reduce 메소드를 사용하여 0에서 시작하는 대신 시작 합계에 5를 더할 것입니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; listOfNumbers = Arrays.asList(1, 2, 3, 4);
int sum = listOfNumbers.parallelStream().reduce(5, Integer::sum);
assertThat(sum).isNotEqualTo(15);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순차스트림에서의 이 작업 결과는 &lt;b&gt;15&lt;/b&gt;입니다. 그러나 reduce 작업이 병렬로 처리되기 때문에 실제 모든 worker 스레드에서 숫자 5가 합산됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dtoy5/btrLiqFpima/S81fQwbX8VibCTMHEeMjs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dtoy5/btrLiqFpima/S81fQwbX8VibCTMHEeMjs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dtoy5/btrLiqFpima/S81fQwbX8VibCTMHEeMjs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDtoy5%2FbtrLiqFpima%2FS81fQwbX8VibCTMHEeMjs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;301&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;534&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;실제 결과는 공통 fork-join 풀에서 사용되는 스레드 수에 따라 다를 수 있습니다. 해당 문제를 해결하기 위해서는, 병렬 스트림 외부에 숫자 5를 더해야합니다.&lt;/p&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;u&gt;해결된 소스코드.&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; listOfNumbers = Arrays.asList(1, 2, 3, 4);
int sum = listOfNumbers.parallelStream().reduce(0, Integer::sum) + 5;
assertThat(sum).isEqualTo(15);&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;&amp;nbsp;&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;b&gt;common thread pool&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;common 스레드풀의 스레드 수는 프로세서 코어 수와 같습니다. 그러나 API를 사용하면 JVM 매개변수를 전달하여 사용할 스레드수를 지정할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;-D java.util.concurrent.ForkJoinPool.common.parallelism=4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 전역으로 설정하는 것이며 common thread pool을 사용하는 모든 병렬 스트림 및 기타 모든 fork-join 작업에 영향을 미친다는 점을 기억해야 합니다. 전역설정으로 인해 더 많은 스레드가 필요한 부분에 4개로 제한하는 꼴이 되버리니까요. 매우 타당한 이유가 있지 않는 한 해당 매개변수를 수정하지 않는 것이 좋습니다.&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;b&gt;custom thread pool&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 common thread pool 외에도 custom thread pool에서 병렬 스트림을 실행할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; listOfNumbers = Arrays.asList(1, 2, 3, 4);
ForkJoinPool customThreadPool = new ForkJoinPool(4);
int sum = customThreadPool.submit(
    () -&amp;gt; listOfNumbers.parallelStream().reduce(0, Integer::sum)).get();
customThreadPool.shutdown();
assertThat(sum).isEqualTo(10);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle은 common Thread pool을 사용하는 것을 권장하고 있습니다. custom thread pool에서 병렬 스트림을 실행하는데에도 마찬가지로 타당한 이유가 있어야 할 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;성능상의 영향&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;&lt;b&gt;오버헤드&lt;/b&gt;&lt;/u&gt;(Overhead)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;integer Stream에 대한 예를 살펴보겠습니다. 순차 스트림과 병렬 스트림의 reduce 메소드에 대한 성능 테스트를 실행하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;IntStream.rangeClosed(1, 100).reduce(0, Integer::sum);
IntStream.rangeClosed(1, 100).parallel().reduce(0, Integer::sum);&lt;/code&gt;&lt;/pre&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;1858&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/As0Qj/btrLiVrYcyV/Vl9TjhFcSq83UewokGtXGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/As0Qj/btrLiVrYcyV/Vl9TjhFcSq83UewokGtXGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/As0Qj/btrLiVrYcyV/Vl9TjhFcSq83UewokGtXGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAs0Qj%2FbtrLiVrYcyV%2FVl9TjhFcSq83UewokGtXGk%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;1858&quot; height=&quot;168&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 때때로 스레드, &lt;u&gt;&lt;b&gt;소스 및 결과를 관리하는 오버헤드가 실제 작업을 수행하는 것보다 cost가 많이 드는 작업&lt;/b&gt;&lt;/u&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;u&gt;&lt;b&gt;분할 비용&lt;/b&gt;&lt;/u&gt;(Splitting Costs)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 소스(각각의 스트림 단위)를 균등하게 분할하는 것은 병렬 처리를 하는데 필요한 cost이지만 일부 데이터 소스는 다른 데이터 소스보다 더 잘 분할됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;ArrayList와 LinkedList를 사용하여 예시를 들어보겠습니다.&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private static final List&amp;lt;Integer&amp;gt; arrayListOfNumbers = new ArrayList&amp;lt;&amp;gt;();
private static final List&amp;lt;Integer&amp;gt; linkedListOfNumbers = new LinkedList&amp;lt;&amp;gt;();

// 해당 코드로 100만의 정수 목록을 초기화합니다.
static {
    IntStream.rangeClosed(1, 1_000_000).forEach(i -&amp;gt; {
        arrayListOfNumbers.add(i);
        linkedListOfNumbers.add(i);
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두가지 유형의 List 구현체에서 순차 및 병렬 reduce 작업에 대한 성능 테스트를 실행해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;arrayListOfNumbers.stream().reduce(0, Integer::sum)
arrayListOfNumbers.parallelStream().reduce(0, Integer::sum);
linkedListOfNumbers.stream().reduce(0, Integer::sum);
linkedListOfNumbers.parallelStream().reduce(0, Integer::sum);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 결과는 순차 스트림을 병렬 스트림으로 변환하는 것이 ArrayList에 대해서만 성능 이점을 가져온다는 것을 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9ovsU/btrLhXKGJLW/qK2veYLGHWvbxoIv3ElZz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9ovsU/btrLhXKGJLW/qK2veYLGHWvbxoIv3ElZz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9ovsU/btrLhXKGJLW/qK2veYLGHWvbxoIv3ElZz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9ovsU%2FbtrLhXKGJLW%2FqK2veYLGHWvbxoIv3ElZz0%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;1848&quot; height=&quot;248&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LinkedList는 병렬처리를 했지만 Score와 Error 값이 증가한 것을 볼 수 있는&amp;nbsp; 반면, ArrayList는 Score와 Error 값이 감소하였습니다.&lt;/p&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;u&gt;&lt;i&gt;&lt;b&gt;Array라는 특징이 저렴하게 소스를 분할&lt;/b&gt;&lt;/i&gt;&lt;/u&gt;할 수 있겠지만 LinkedList에는 이러한 속성이 없기 때문입니다. TreeMap과 HashSet은 LinkedList보다 잘 분할되지만 Array만큼은 분할되지 않습니다.&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;u&gt;&lt;b&gt;병합 비용&lt;/b&gt;&lt;/u&gt;(Merging Costs)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 계산을 하기 위해 소스를 분할할 때마다 결국 결과를 병합해야 합니다. 서로 다른 병합 작업으로 sum 및 grouping을 사용하여 순차 및 병렬 스트림에서의 성능 테스트를 실행해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;arrayListOfNumbers.stream().reduce(0, Integer::sum);
arrayListOfNumbers.stream().parallel().reduce(0, Integer::sum);
arrayListOfNumbers.stream().collect(Collectors.toSet());
arrayListOfNumbers.stream().parallel().collect(Collectors.toSet());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 결과는 순차 스트림을 병렬 스트림으로 변환하는 것이 sum 연산에 대해서만 성능상의 이점을 가져온다는 것을 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FND8i/btrLgZiaKn7/TkKI2koXkHGNg8YeEY3To0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FND8i/btrLgZiaKn7/TkKI2koXkHGNg8YeEY3To0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FND8i/btrLgZiaKn7/TkKI2koXkHGNg8YeEY3To0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFND8i%2FbtrLgZiaKn7%2FTkKI2koXkHGNg8YeEY3To0%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;1848&quot; height=&quot;248&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병합 작업은 reduce 와 sum 과 같은 일부 작업의 경우 정말 가볍게 동작하지만 &lt;u&gt;&lt;b&gt;set과 map과 같은 형태로 그룹화하는 것은 병합 작업이 상당히 무겁게 동작&lt;/b&gt;&lt;/u&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;b&gt;Memory Locality&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 컴퓨터는 정교한 멀티 레벨 캐시를 활용하여 자주 사용하는 데이터를 프로세서 가까이에 저장합니다. 선형 메모리 엑세스 패턴이 감지되면 하드웨어는 데이터가 곧 필요할 것이라는 가정 하에 다음 데이터 라인을 미리 가져옵니다.(prefetch)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 처리는 프로세서 코어가 유용한 작업을 지속적으로 처리할 수 있을 때 성능 이점을 가져옵니다. cache miss를 기다리는 것은 유용한 작업이 아니기 때문에 메모리 대역폭을 제한 요소로 고려해야 합니다.&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;하나는 primitive 타입을 사용하고 다른 하나는 wrapper 타입을 사용하는 두개의 배열을 사용하여 이를 증명해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662176082724&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static final int[] intArray = new int[1_000_000];
private static final Integer[] integerArray = new Integer[1_000_000];

static {
    IntStream.rangeClosed(1, 1_000_000).forEach(i -&amp;gt; {
        intArray[i-1] = i;
        integerArray[i-1] = i;
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 배열에서 순차 및 병렬 reduce 작업에 대한 성능 테스트를 실행해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;Arrays.stream(intArray).reduce(0, Integer::sum);
Arrays.stream(intArray).parallel().reduce(0, Integer::sum);
Arrays.stream(integerArray).reduce(0, Integer::sum);
Arrays.stream(integerArray).parallel().reduce(0, Integer::sum);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 결과는 순차 스트림을 병렬 스트림으로 변환하는 것이 primitive 배열을 사용할 때 약간 더 많은 성능 이점을 가져온다는 것을 보여줍니다. &lt;u&gt;(둘 다 성능상에 이점은 있었지만 &lt;b&gt;primitive 타입&lt;/b&gt;이 더 컸다.)&lt;/u&gt;&lt;b&gt;&amp;nbsp;&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;결과적으로 primitive int 타입의 값이 가장 score가 적습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wNEhO/btrLg8MSXEb/ybl1R8DjCf9XKIXl7jnBxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wNEhO/btrLg8MSXEb/ybl1R8DjCf9XKIXl7jnBxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wNEhO/btrLg8MSXEb/ybl1R8DjCf9XKIXl7jnBxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwNEhO%2FbtrLg8MSXEb%2Fybl1R8DjCf9XKIXl7jnBxk%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;1844&quot; height=&quot;280&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 Array는 Java에서 가장 최고의 locality를 제공합니다. 일반적으로 &lt;b&gt;&lt;u&gt;데이터 구조에 포인터가 많을수록 참조 객체를 가져오기 위해 메모리에 더 많은 압력&lt;/u&gt;&lt;/b&gt;을 가합니다.&lt;i&gt;&lt;u&gt;(여기서 포인터라는 것은 참조로 이해하면 좋을 것 같습니다. primitive 타입은 값 자체를 다루는 타입이고 Wrapper 타입은 결국 인스턴스가 들어있는 참조이기 때문입니다.&lt;/u&gt;&lt;/i&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;NQ 모델&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오라클은 병렬 처리가 성능 향상을 제공할 수 있는지 여부를 결정하는데 도움이 되는 간단한 모델을 제시했습니다. NQ 모델에서는 &lt;b&gt;N&lt;/b&gt;은 &lt;u&gt;소스 데이터의 요소의 수&lt;/u&gt;를 나타내고 &lt;b&gt;Q&lt;/b&gt;는 &lt;u&gt;데이터 요소당 수행된 계산의 양&lt;/u&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;N * Q의 곲이 클수록 병렬화로 인해 성능 향상의 가능성이 높아 집니다. 숫자 합산과 같이 사소한 Q가 있는 문제의 경우 경험상 N은 10,000보다 커야 합니다. (List 요소의 size() 값이 1만은 넘어야 성능효과가 미친다는 것.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;따라서 계산 수가 증가하면 병렬 처리를 통해 성능을 높이는 데 필요한 데이터 크기가 줄어듭니다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 복잡한 단계의 계산일수록 상대적으로 N(요소의 수)값이 낮아져도 성능 향상에 대한 효과를 얻을 수 있다는 표현인듯?&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;b&gt;파일 검색 비용&lt;/b&gt;&lt;/h4&gt;
&lt;div&gt;병렬 스트림을 사용한 파일 검색은 순차 스트림에 비해 더 나은 성능을 보일 수 있습니다.&lt;/div&gt;
&lt;div&gt;예시로, 1500개가 넘는 텍스트 파일을 검색하기 위해 순차 및 병렬 스트림에 대한 성능 테스트를 실행해보겠습니다.&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 순차 스트림 처리
Files.walk(Paths.get(&quot;src/main/resources/&quot;)).map(Path::normalize).filter(Files::isRegularFile)
      .filter(path -&amp;gt; path.getFileName().toString().endsWith(&quot;.txt&quot;)).collect(Collectors.toList());

// 병렬 스트림 처리
Files.walk(Paths.get(&quot;src/main/resources/&quot;)).parallel().map(Path::normalize).filter(Files::
      isRegularFile).filter(path -&amp;gt; path.getFileName().toString().endsWith(&quot;.txt&quot;)).
      collect(Collectors.toList());​&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;1454&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LHPPK/btrLh8yA1Kt/k5LcEIKLkRgWy4KeouvkMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LHPPK/btrLh8yA1Kt/k5LcEIKLkRgWy4KeouvkMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LHPPK/btrLh8yA1Kt/k5LcEIKLkRgWy4KeouvkMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLHPPK%2FbtrLh8yA1Kt%2Fk5LcEIKLkRgWy4KeouvkMK%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;1454&quot; height=&quot;164&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;164&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;병렬 스트림을 사용하는 경우&lt;/b&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;u&gt;&lt;b&gt;개발중에는 순차 스트림을 계속 기본으로 사용해야 합니다.&lt;/b&gt;&lt;/u&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;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 포스팅에서는 자바에서 순차 스트림과 병렬 스트림의 차이점에 대해서 살펴보았습니다. 병렬 스트림이 기본 fork-join pool과 해당 worker 스레드를 사용한다는 것을 배웠습니다.&lt;/p&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;u&gt;&lt;b&gt;Array가 가능한 최고의 지역성을 제공하고 저렴하고 균등하게 분할할 수 있기 때문에 병렬 처리를 위한 훌륭한 데이터 소스라는 것을 알았습니다.&lt;/b&gt;&lt;/u&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;마지막으로 NQ모델을 살펴보고 실제 성능 요구사항이 있는 경우에만 병렬 스트림을 사용하는 것이 좋다는 것을 알았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Java</category>
      <category>java memory locality</category>
      <category>java nq model</category>
      <category>java Parallel stream</category>
      <category>Java Stream</category>
      <category>자바 병렬 스트림</category>
      <category>자바 순차 스트림</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/461</guid>
      <comments>https://sas-study.tistory.com/461#entry461comment</comments>
      <pubDate>Sat, 3 Sep 2022 13:02:24 +0900</pubDate>
    </item>
    <item>
      <title>[번역글] Pattern: Strangler application 스트랭글러 패턴</title>
      <link>https://sas-study.tistory.com/460</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 포스팅은 &lt;a href=&quot;https://microservices.io/patterns/refactoring/strangler-application.html&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;b&gt;문제&lt;/b&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;b&gt;해결방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레거시&lt;span&gt; &lt;/span&gt;애플리케이션을&lt;span&gt; &lt;/span&gt;중심으로&lt;span&gt; &lt;/span&gt;새로운&lt;span&gt;(srangler) &lt;/span&gt;애플리케이션을&lt;span&gt; &lt;/span&gt;점진적으로&lt;span&gt; &lt;/span&gt;개발하여&lt;span&gt; &lt;/span&gt;애플리케이션을&lt;span&gt; &lt;/span&gt;현대화합니다&lt;span&gt;. &lt;/span&gt;이&lt;span&gt; &lt;/span&gt;시나리오에서&lt;span&gt; srangler &lt;/span&gt;애플리케이션에는&lt;span&gt; &lt;/span&gt;마이크로서비스&lt;span&gt; &lt;/span&gt;아키텍처가&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/up0f4/btrLhXDfwJy/XB3aZDrkdF2Qe8IpQXGp7k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/up0f4/btrLhXDfwJy/XB3aZDrkdF2Qe8IpQXGp7k/img.jpg&quot; data-alt=&quot;모놀리스를 교살한다는 의미 -&amp;amp;gt; 모놀리스 어플리케이션을 점차 죽인다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/up0f4/btrLhXDfwJy/XB3aZDrkdF2Qe8IpQXGp7k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fup0f4%2FbtrLhXDfwJy%2FXB3aZDrkdF2Qe8IpQXGp7k%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;1024&quot; height=&quot;768&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모놀리스를 교살한다는 의미 -&amp;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;Strangler 어플리케이션은 두가지 서비스 유형으로 되어있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 이전에 모놀리스 어플리케이션에서 사용되던 기능을 가진 서비스&lt;/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;span&gt; &lt;/span&gt;사용함으로써&lt;span&gt; &lt;/span&gt;비즈니스&lt;span&gt;(&lt;/span&gt;회사&lt;span&gt;)&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;마이크로&lt;span&gt; &lt;/span&gt;서비스를&lt;span&gt; &lt;/span&gt;사용해야함에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;가치를&lt;span&gt; &lt;/span&gt;증명해보일&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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://docs.microsoft.com/ko-kr/azure/architecture/patterns/strangler-fig&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.microsoft.com/ko-kr/azure/architecture/patterns/strangler-fig&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1662112362487&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;스트랭글러 무화과 패턴 - Azure Architecture Center&quot; data-og-description=&quot;특정 기능을 새로운 애플리케이션 및 서비스로 점진적으로 교체하여 레거시 시스템을 단계적으로 마이그레이션합니다.&quot; data-og-host=&quot;docs.microsoft.com&quot; data-og-source-url=&quot;https://docs.microsoft.com/ko-kr/azure/architecture/patterns/strangler-fig&quot; data-og-url=&quot;https://docs.microsoft.com/ko-kr/azure/architecture/patterns/strangler-fig&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/coUwm9/hyPFYQYN2F/MkuptHrGXkAxq4DGFor7gk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bg0LOl/hyPEGEnjjp/7akTQcuqBr2Qh5dLrWuIwK/img.png?width=1833&amp;amp;height=552&amp;amp;face=0_0_1833_552&quot;&gt;&lt;a href=&quot;https://docs.microsoft.com/ko-kr/azure/architecture/patterns/strangler-fig&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.microsoft.com/ko-kr/azure/architecture/patterns/strangler-fig&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/coUwm9/hyPFYQYN2F/MkuptHrGXkAxq4DGFor7gk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bg0LOl/hyPEGEnjjp/7akTQcuqBr2Qh5dLrWuIwK/img.png?width=1833&amp;amp;height=552&amp;amp;face=0_0_1833_552');&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;스트랭글러 무화과 패턴 - Azure Architecture Center&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;docs.microsoft.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>흑구의 공부내용 공유</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/460</guid>
      <comments>https://sas-study.tistory.com/460#entry460comment</comments>
      <pubDate>Fri, 2 Sep 2022 18:51:29 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot server.shutdown graceful 옵션</title>
      <link>https://sas-study.tistory.com/459</link>
      <description>&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;오늘은 Spring Boot 서버가 종료될 경우&lt;/p&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 boot 서버였고 &lt;u&gt;&lt;b&gt;서버A&lt;/b&gt;&lt;/u&gt;라 지칭하겠습니다. 네트워크 타임아웃에 대해서 30초의 간략한 시간으로 설정해두었습니다.&lt;/p&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 boot 서버였고 &lt;b&gt;서버B&lt;/b&gt;라 지칭하겠습니다. &lt;u&gt;&lt;b&gt;서버A&lt;/b&gt;&lt;/u&gt;에 의해 다수의 요청을 받는 환경에서 &lt;b&gt;서버B&lt;/b&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;u&gt;&lt;b&gt;서버A&lt;/b&gt;&lt;/u&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;서버B&lt;/b&gt;의 종료가 급격하게 이루어짐으로써 몇개의 요청에 대한 응답이 유실되었기 때문인데요. 이것은 서버의 종료(shutdonw) 속성이 &lt;i&gt;&lt;u&gt;IMMEDIATE&lt;/u&gt;&lt;/i&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;결국 서버B의 종료에 의해 &lt;u&gt;서버A(클라이언트 서버)는 응답을 받지 못한채 타임아웃 처리&lt;/u&gt;되어버리고 말았습니다. 서버A의 스레드는 해당 타임아웃 만큼의 스레드 N개에 대한 손실을 볼 수밖에 없습니다.(비동기 클라이언트가 아니라면..)&lt;/p&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;u&gt;GRACEFUL &lt;/u&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;또한 타임아웃도 추가할 수 있습니다. (graceful 하게 기존 요청을 처리할 수 있도록 시간을 유예하는 것)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lifecycle.timeout-per-shutdown-phase = 10s&lt;/p&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개의 요청이 남아있는 상태에서 10초 동안 해당 요청을 모두 처리하고서 서버를 종료하게 됩니다.&lt;/p&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개의 요청이 11초가 걸리는 환경이라면 1초동안에 처리할 수 있는 갯수의 요청에 대해서는 클라이언트 서버가 응답을 받지 못하므로 해당 속성값에 대한 seconds 값은 적절하게 조절해야 할 것 같습니다.&lt;/p&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;application.properties&lt;/b&gt;를 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=20s&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;b&gt;application.yml&lt;/b&gt;을 활용&lt;/p&gt;
&lt;pre id=&quot;code_1662109779234&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 20s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;해당 설정에 대한 enum으로 spring.boot.web.server 패키지에 정의되어있습니다. ( 해당 설정은 spring boot 2.3 이상에서 가능합니다. )&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;public enum Shutdown {

   /**
    * The {@link WebServer} should support graceful shutdown, allowing active requests
    * time to complete.
    */
   GRACEFUL,

   /**
    * The {@link WebServer} should shut down immediately.
    */
   IMMEDIATE;

}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 개발/Spring Boot</category>
      <category>server.shutdown</category>
      <category>shutdown graceful</category>
      <category>spring boot shutdown</category>
      <category>timeout-per-shutdown-phase</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/459</guid>
      <comments>https://sas-study.tistory.com/459#entry459comment</comments>
      <pubDate>Fri, 2 Sep 2022 18:10:50 +0900</pubDate>
    </item>
    <item>
      <title>Postgresql 전체 테이블 목록 조회하기</title>
      <link>https://sas-study.tistory.com/458</link>
      <description>&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;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;그러기 위해서는 Dababase에 있는 테이블들부터 우선 구분을 할 필요가 있었다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 전체 테이블이 800개가 넘어가는 모놀리틱한 데이터베이스의 전체 테이블을 파일로 저장하기 위해서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-6696145815750706&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 쿼리를 확인해보았더니&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;SELECT * FROM pg_catalog.pg_tables;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 schema의 테이블이 조회되어 시스템 테이블도 함께 조회된다. (pg_catalog)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 where절에 다음과 같은 조건을 붙여 결과를 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;SELECT * FROM pg_catalog.pg_tables where schemaname = 'public';&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/Postgresql</category>
      <category>postgresql pg_catalog</category>
      <category>postgresql 전체 테이블 조회</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/458</guid>
      <comments>https://sas-study.tistory.com/458#entry458comment</comments>
      <pubDate>Fri, 2 Sep 2022 16:52:40 +0900</pubDate>
    </item>
    <item>
      <title>mysql 컬럼 추가 / 변경 삭제 쿼리 정리 (ALTER TABLE)</title>
      <link>https://sas-study.tistory.com/457</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;개발하다가 너무 datagrip의 인터페이스로 제공되는 ddl을 많이 사용하는 것 같아서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 스키마를 sql only로 처리하려고 노력하고 있는데..(누군가는 필요없다고 생각할 수 있지만 개인적인 챌린지입니다!)&lt;/p&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보단 postgresql을 많이 사용했다보니 개인 프로젝트 할 때 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;먼저 예제로 사용할 테이블을 만들어 보겠습니다.&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;b&gt;테이블 생성&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;create table room(
    seq bigint primary key,
    type varchar(100),
    status varchar(100)
);&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;b&gt;컬럼 추가(컬럼 위치)&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;ALTER TABLE 테이블 ADD 컬럼명 타입 DEFAULT 디폴트값;
ALTER TABLE 테이블 ADD COLUMN 컬럼명 타입 DEFAULT 디폴트값 컬럼위치;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Postgresql과는 다르게 컬럼 위치를 정할 수 있었다. postgresql에서는 새로운 테이블을 만들어서 마이그레이션하는 방법밖에는 생각이 안났는데 개발하면서 스키마가 자주 바뀔 수 있는 상황에서 좀더 스키마의 자연스러운 형태를 유지할 수 있을 것 같다.&lt;/p&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;u&gt;&lt;b&gt;컬럼 위치 변경&lt;/b&gt;&lt;/u&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;ex )&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;ALTER TABLE room ADD address varchar(200);
ALTER TABLE room ADD address varchar(200) DEFAULT '매물주소';
ALTER TABLE room ADD COLUMN  address varchar(200) DEFAULT '매물주소' FIRST;&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;컬럼명, 컬럼 타입 변경&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1661920958358&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE 테이블 CHANGE 기존컬럼명 변경할컬럼명 타입;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경함에 있어서 &lt;u&gt;&lt;b&gt;change&lt;/b&gt;&lt;/u&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;ex) 아래처럼 address 를 text 타입으로 변경하니 타입 또한 변경할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;ALTER TABLE room CHANGE address address_fix text;&lt;/code&gt;&lt;/pre&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;b&gt;컬럼 default 값 변경&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1661921416623&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE 테이블 MODIFY 컬럼명 컬럼의기존타입 DEFAULT 디폴트값;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex ) default 값을 변경한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;ALTER TABLE room MODIFY address varchar(200) default '디폴트';&lt;/code&gt;&lt;/pre&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;b&gt;컬럼 주석 추가&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;ALTER TABLE room MODIFY address varchar(200) DEFAULT '디폴트를 유지시켜야한다.' comment '주소 컬럼에 코멘트를 추가한다.';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬럼에 주석을 추가할 경우에는 조금 불편하지만 기존 속성을 모두 추가해주어야 하는 것 같다. 이건 조금 불편하다.. datagrip 자동 ddl에서 확인해도 기존의 속성을 물고 해당 ddl이 실행되는 걸 보니 mysql dbms의 한계가 아닌가 싶다.&amp;nbsp;&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;b&gt;컬럼 위치 변경&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1661921118301&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE 테이블 MODIFY COLUMN 컬럼명 타입 AFTER 다른컬럼;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) address라는 컬럼을 seq바로 뒤에 위치시키고 싶다면 위와 같이 처리하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1661921080501&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE room MODIFY COLUMN address varchar(200) AFTER seq;&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;컬럼 삭제&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1661921215740&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE 테이블 DROP COLUMN 삭제할컬럼명;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) address 라는 컬럼을 삭제한다.&lt;/p&gt;
&lt;pre id=&quot;code_1661921196692&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE room DROP COLUMN address;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Database</category>
      <category>mysql ddl</category>
      <category>mysql DDL SQL 컬럼</category>
      <category>mysql 컬럼</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/457</guid>
      <comments>https://sas-study.tistory.com/457#entry457comment</comments>
      <pubDate>Wed, 31 Aug 2022 13:55:57 +0900</pubDate>
    </item>
    <item>
      <title>Spring Framework에서 생성자 주입을 권장하는 이유!</title>
      <link>https://sas-study.tistory.com/456</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;오늘은 스프링 프레임워크를 사용할 때, 반드시 숙지해야되는 개념인 DI(Dependency Injection), 의존주입에 대한 이야기를 진행해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 DI라는 개념은 스프링에만 존재하는 개념은 아닙니다. 기존에 다른 프레임워크에서부터 발전해온 개념으로 IOC를 구현하기 위해 사용되던 방법으로 DI를 써왔는 흐름으로 알고 있습니다.&lt;/p&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에서 봤던 DI와 기존 DI간의 매치가 아직 안되신분들이 있을 것 같아서 그것부터 바로잡아 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Dependency Injection&lt;/b&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;&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 MVC를 토대로 개발을 해왔던 분이시라면 충분히 아시겠지만 다시 정리하는 차원에서&lt;/p&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_1661834869312&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Controller {

    private Service service;

    public String doSomething() {
        return service.doSomething();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller 클래스는 doSomething 메소드를 실행하기 위해서는 Service 클래스에 의존하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 참조관계를 의존이라고 표현하며 해당 의존객체의 인스턴스를 주입하기 위한 방법이 의존주입입니다.(물론 단순히 코드상에서 new&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;기존에는 이런 의존주입을 할 수 있는 방법으로 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;b&gt;&lt;u&gt;수정자&lt;/u&gt;&lt;/b&gt;를 통한 주입(Setter)&lt;/p&gt;
&lt;pre id=&quot;code_1661835029316&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Controller {

    private Service service;

    public void setService(Service service) {
    	this.service = service;
    }

    public String doSomething() {
        return service.doSomething();
    }

}&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;2. &lt;u&gt;&lt;b&gt;생성자&lt;/b&gt;&lt;/u&gt;를 통한 주입(Constructor)&lt;/p&gt;
&lt;pre id=&quot;code_1661835087002&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Controller {

    private Service service;

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

    public String doSomething() {
        return service.doSomething();
    }

}&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;&amp;nbsp;&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;이렇게 두개의 과정밖에 나뉘어지지 않습니다. 다만 두개의 주입방법이 어떤 특징을 갖는지는 setter와 생성자의 특징을 그대로 이해만 했다면 쉽게 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정자를 사용하게 된다면 인스턴스화 후 주입단계를 거치지 않게 된다면 doSomething 메소드에서 NullPointerException이 발생할 가능성이 생기게 됩니다. 다만 생성자를 쓰게된다면 직접적으로 null을 주입하지 않는 이상 인스턴스화조차 일어날 수 없으니 NullPointerException에 대한 고려는 하지 않겠죠..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Spring Dependency Injection&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 스프링에서의 DI라고 크게 다르지는 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 필드 주입이라는 타입기반의 의존주입이 추가됩니다. @Autowired 어노테이션의 동작방식은 아래의 참조링크를 확인해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://beststar-1.tistory.com/40&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://beststar-1.tistory.com/40&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1661836024113&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;@Autowired의 동작원리&quot; data-og-description=&quot;@Autowired란? 의존관계 주입(DI)을 할 때 사용하는 어노테이션(Annotation)이며, 의존 객체의&amp;nbsp;타입에 해당하는 빈(Bean)을 찾아 주입하는 역할을 한다.   의존관계 주입에 대해서는 IoC(Inversion of Control,&quot; data-og-host=&quot;beststar-1.tistory.com&quot; data-og-source-url=&quot;https://beststar-1.tistory.com/40&quot; data-og-url=&quot;https://beststar-1.tistory.com/40&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bOoGST/hyPBSyqxZr/o2BMjHaJFr0ckI6dOP6bGk/img.png?width=800&amp;amp;height=328&amp;amp;face=0_0_800_328,https://scrap.kakaocdn.net/dn/mtz0M/hyPBH4J2Vc/6MuRhA6uHGcogaVK8BuKi1/img.png?width=800&amp;amp;height=328&amp;amp;face=0_0_800_328,https://scrap.kakaocdn.net/dn/fqTp9/hyPBPIsPcP/HQlq1QWQnsH1fNuSmfsjY0/img.png?width=1166&amp;amp;height=615&amp;amp;face=0_0_1166_615&quot;&gt;&lt;a href=&quot;https://beststar-1.tistory.com/40&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://beststar-1.tistory.com/40&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bOoGST/hyPBSyqxZr/o2BMjHaJFr0ckI6dOP6bGk/img.png?width=800&amp;amp;height=328&amp;amp;face=0_0_800_328,https://scrap.kakaocdn.net/dn/mtz0M/hyPBH4J2Vc/6MuRhA6uHGcogaVK8BuKi1/img.png?width=800&amp;amp;height=328&amp;amp;face=0_0_800_328,https://scrap.kakaocdn.net/dn/fqTp9/hyPBPIsPcP/HQlq1QWQnsH1fNuSmfsjY0/img.png?width=1166&amp;amp;height=615&amp;amp;face=0_0_1166_615');&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;@Autowired의 동작원리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;@Autowired란? 의존관계 주입(DI)을 할 때 사용하는 어노테이션(Annotation)이며, 의존 객체의&amp;nbsp;타입에 해당하는 빈(Bean)을 찾아 주입하는 역할을 한다.   의존관계 주입에 대해서는 IoC(Inversion of Control,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;beststar-1.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;p data-ke-size=&quot;size16&quot;&gt;1. 수정자를 통한 주입&lt;/p&gt;
&lt;pre id=&quot;code_1661836313051&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequestMapping(&quot;/di&quot;)
@RestController
public class FieldInjectionController {

    private FieldInjectionService fieldInjectionService;

    @Autowired
    public void setFieldInjectionService(FieldInjectionService fieldInjectionService) {
        this.fieldInjectionService = fieldInjectionService;
    }

    @GetMapping(&quot;/field-injection&quot;)
    public String fieldInjection() {
        fieldInjectionService.fieldInjection();
        return &quot;fieldInjection&quot;;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 생성자를 통한 주입&lt;/p&gt;
&lt;pre id=&quot;code_1661836344359&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequestMapping(&quot;/di&quot;)
@RestController
public class FieldInjectionController {

    private FieldInjectionService fieldInjectionService;

    public FieldInjectionController(FieldInjectionService fieldInjectionService) {
        this.fieldInjectionService = fieldInjectionService;
    }

    @GetMapping(&quot;/field-injection&quot;)
    public String fieldInjection() {
        fieldInjectionService.fieldInjection();
        return &quot;fieldInjection&quot;;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 생성자 주입은 lombok을 활용하여 생성자를 처리할 수 있기 때문에 아래와 같이 하기도 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1661836384825&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@RequestMapping(&quot;/di&quot;)
@RestController
public class FieldInjectionController {

    private final FieldInjectionService fieldInjectionService;

    @GetMapping(&quot;/field-injection&quot;)
    public String fieldInjection() {
        fieldInjectionService.fieldInjection();
        return &quot;fieldInjection&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;근데 여기서 한가지 주목해야할 부분이 있습니다. 생성자 주입에서 의존객체에 final 키워드가 붙었습니다. 왜 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;여기서는 Spring의 Bean의 개념까지 알아야 이해가 될겁니다. 스프링의 빈이란 상태가 별도로 없는 일종의 로직을 처리하는 인스턴스에 불과합니다. 따라서 멱등성이라고 하는 개념을 만족해야하는 인스턴스만이 스프링 빈으로 등록되어야 합니다.(물론 라이프사이클마다 다르지만.. 싱글톤이라는 기본 속성하에 이야기하겠습니다.)&lt;/p&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 키워드로 방어처리를 할 수 있게 되는것입니다. 싱글톤 라이프사이클로 관리하고 있는 스프링 빈이 있다면 절대 변경되어서는 안될 인스턴스인 것 입니다. 그런데 어플리케이션이 런타임되는 과정에서 주입되는 인스턴스가 변경되는 setter를 둔다는건 굉장한 리스크를 갖고 개발해야하는 것입니다. 개발자들에 의해 컨벤션이 setter 주입 메소드는 건들지 않는다고 할지라도 정성적인 시스템과 절대로 호출할 수 없는 물리적인 시스템에는 분명한 한계가 있게 됩니다.&lt;/p&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 키워드를 기존의 의존주입의 영역에서 이야기하면 사실 애매합니다. 의존성이 스프링 빈으로 관리될지 알 수 없을 뿐더러 의존객체가 인터페이스라면 전략패턴으로 구현된 셋팅에 의해 다르게 설정되어야 되는 요구사항이 필요하다면 setter가 필요하게 되어 인스턴스 구현체가 변경될 수 있어야 하니까요. 이런 구조에서는 final에 의한 immutable이 말이 되지 않습니다. immutable에 대한 이야기는 스프링의 아키텍쳐내에서 라이프사이클이 싱글톤인 스프링 빈 인스턴스를 의존주입할 때만 immutable이라는 키워드를 사용해도 좋지 않나 생각합니다.&lt;/p&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;pre id=&quot;code_1661836678141&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequestMapping(&quot;/di&quot;)
@RestController
public class FieldInjectionController {

    @Autowired
    private FieldInjectionService fieldInjectionService;

    @GetMapping(&quot;/field-injection&quot;)
    public String fieldInjection() {
        fieldInjectionService.fieldInjection();
        return &quot;fieldInjection&quot;;
    }

}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순환참조.(Circular Reference)&lt;/p&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_1661837176402&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class FieldInjectionService {

    @Autowired
    private FieldInjection2Service fieldInjection2Service;

    public int doSomething() {
        System.out.println(&quot;fieldInjectionService.doSomething() 호출&quot;);
        return fieldInjection2Service.doSomething();
    }
}

@Service
public class FieldInjection2Service {

    @Autowired
    private FieldInjectionService fieldInjectionService;

    public int doSomething() {
        int fieldInjectionServiceResult = fieldInjectionService.doSomething();
        System.out.println(&quot;fieldInjection2Service.doSomething() 호출&quot;);
        return fieldInjectionServiceResult;
    }
}&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;&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-origin-width=&quot;1712&quot; data-origin-height=&quot;1202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxWYuG/btrKUdHSn3m/S928y1gC5mroz198hNx7JK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxWYuG/btrKUdHSn3m/S928y1gC5mroz198hNx7JK/img.png&quot; data-alt=&quot;순환참조 때문에 발생하는 에러.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxWYuG/btrKUdHSn3m/S928y1gC5mroz198hNx7JK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxWYuG%2FbtrKUdHSn3m%2FS928y1gC5mroz198hNx7JK%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;1712&quot; height=&quot;1202&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;1202&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;[ * 스프링 부트 2.6 버전 이후로는 스프링을 구동할 때 순환참조에 대한 이슈가 발생하였다고 구동자체를 못시키게 하는 업데이트 내역이 존재합니다만 스프링부트 2.6 미만의 기존 레거시 프로그램에서는 충분히 순환참조가 발생할 수 있습니다. ]&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 생성자 주입은 어떻게 될까요??&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2706&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjYd8P/btrKTSYkwJU/NojtKZC9ZDMyKKSmMTtUO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjYd8P/btrKTSYkwJU/NojtKZC9ZDMyKKSmMTtUO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjYd8P/btrKTSYkwJU/NojtKZC9ZDMyKKSmMTtUO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjYd8P%2FbtrKTSYkwJU%2FNojtKZC9ZDMyKKSmMTtUO0%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;2706&quot; height=&quot;738&quot; data-origin-width=&quot;2706&quot; data-origin-height=&quot;738&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;생성자끼리 서로 참조하고 있기 때문에 인스턴스를 생성하지 못하는 이슈를 어느 단계에서 캐치하게 되고 트리거가 발생하여 해당 구동 실패를 띄우는게 아닌가 싶습니다.&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;따라서 스프링 가이드에서는 항상 생성자 주입을 통한 개발을 권장하고 있고 이에 대한 어조는 굉장히 강력합니다. always라는 표현을 썻었기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 의존주입과 스프링에서의 DI 까지 일련의 흐름에 대해서 쭉 살펴봤고 관련 자료들도 훑어보면서 구분해야되는 부분과 오해하지 말아야 하는 부분에 대해서 일련의 내용으로 구성해보았습니다.&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;</description>
      <category>웹 개발/Spring Boot</category>
      <category>DI 객체</category>
      <category>DI 의존주입</category>
      <category>스프링 생성자주입</category>
      <category>스프링 의존주입</category>
      <category>스프링 필드주입</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/456</guid>
      <comments>https://sas-study.tistory.com/456#entry456comment</comments>
      <pubDate>Tue, 30 Aug 2022 14:38:42 +0900</pubDate>
    </item>
    <item>
      <title>이 정도면 취업이 가능할까요?</title>
      <link>https://sas-study.tistory.com/455</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;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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;절대적으로 어렵다고 생각하는게 좋을 것 같습니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;신입개발자를 지향하시는 분들 대부분이 실제 프로젝트에 투입된다면&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;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;&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;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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;u&gt;&lt;b&gt;이&amp;nbsp;정도면&amp;nbsp;취업이&amp;nbsp;가능할까요?&lt;/b&gt;&lt;/u&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;u&gt;사소한 부분&lt;/u&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;절대적으로 어렵다고 생각하는게 좋을 것 같습니다.&lt;/b&gt;&lt;/u&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;할수 있다. 노력. 열정. 미래가능성.&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;오히려 이런 키워드가 많을 것 같습니다.&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;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;u&gt;&lt;b&gt;지금으로도 충분할 것 같은데요?&lt;/b&gt;&lt;/u&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;IT의 역사도 상당히 밀도있게 진행되었습니다. 그 밀도 있는 역사를 모두 알고 지나가는 신입&lt;u&gt;(신입보단 하이퍼 시니어가 되겠네요..)&lt;/u&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 대답은 항상. &lt;b&gt;&lt;u&gt;절대적으로 어렵다고 생각하는게 좋을 것 같습니다.&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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.youtube.com/watch?v=PC_o_PbdrLk&quot;&gt;https://www.youtube.com/watch?v=PC_o_PbdrLk&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=PC_o_PbdrLk&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/K5BCj/hyPBUWGp4l/NJLbC9kO8c8VNNit5xPXNk/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/PC_o_PbdrLk&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>흑구의 정신세계</category>
      <category>신입개발자 취업</category>
      <category>취업</category>
      <category>취업 가능</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/455</guid>
      <comments>https://sas-study.tistory.com/455#entry455comment</comments>
      <pubDate>Mon, 29 Aug 2022 15:31:13 +0900</pubDate>
    </item>
    <item>
      <title>인프콘 2022 다녀와서(벨로퍼트님, 개발자의 셀프 브랜딩 세션 후기)</title>
      <link>https://sas-study.tistory.com/454</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2022년 8월 26일 인프런 컨퍼런스에 다녀와서 벨로그를 개발하신 벨로퍼트님의 발표세션을 들었습니다.&lt;/p&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;&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;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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;가장 기억에 남는 문구는 &quot;돈 보다는 자신의 콘텐츠가 가지는 영향력을 즐겨라&quot; 였던 것 같습니다.&lt;/p&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;/p&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;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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;&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;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 내용 감사히 잘 전달받았습니다.&lt;/p&gt;</description>
      <category>흑구의 정신세계</category>
      <category>벨로퍼트 개발자 셀프 브랜딩</category>
      <category>벨로퍼트 김민준</category>
      <category>인프콘 2022 후기</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/454</guid>
      <comments>https://sas-study.tistory.com/454#entry454comment</comments>
      <pubDate>Mon, 29 Aug 2022 11:32:31 +0900</pubDate>
    </item>
    <item>
      <title>Caused by: org.apache.ibatis.type.TypeException: Could not resolve type alias (MyBatis 에러)</title>
      <link>https://sas-study.tistory.com/453</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Mybatis 관련 예제를 작성하던 중 class 이름을 변경해야하는 일이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 익숙하게 인텔리제이의 기능을 활용하여 (Shift + F6) 클래스를 수정했는데..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 클래스가 어디엔가 String 타입으로 선언되어 있다면.. 해당 클래스 이름도 바꿀지 말지에 대한 창이 나옵니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Caused&amp;nbsp;by:&amp;nbsp;org.apache.ibatis.type.TypeException:&amp;nbsp;Could&amp;nbsp;not&amp;nbsp;resolve&amp;nbsp;type&amp;nbsp;alias&amp;nbsp;'co&lt;br /&gt;m.xxx.xxx.XXXApplication.Test'.&lt;br /&gt;&amp;nbsp;&amp;nbsp;Cause:&amp;nbsp;java.lang.ClassNotFoundException:&amp;nbsp;Cannot&amp;nbsp;find&amp;nbsp;class:&amp;nbsp;&lt;br /&gt;co&lt;br /&gt;m.xxx.xxx.XXXApplication.Test&lt;br /&gt;&lt;br /&gt;at&amp;nbsp;org.apache.ibatis.type.TypeAliasRegistry.resolveAlias(TypeAliasRegistry.java:120)&lt;br /&gt;at&amp;nbsp;org.apache.ibatis.builder.BaseBuilder.resolveAlias(BaseBuilder.java:149)&lt;br /&gt;at&amp;nbsp;org.apache.ibatis.builder.BaseBuilder.resolveClass(BaseBuilder.java:116)&lt;br /&gt;...&amp;nbsp;145&amp;nbsp;more&lt;br /&gt;Caused by: java.lang.ClassNotFoundException: Cannot find class:&amp;nbsp;&lt;br /&gt;co&lt;br /&gt;m.xxx.xxx.XXXApplication.Test&lt;br /&gt;&lt;br /&gt;at&amp;nbsp;org.apache.ibatis.io.ClassLoaderWrapper.classForName(ClassLoaderWrapper.java:196)&lt;br /&gt;at&amp;nbsp;org.apache.ibatis.io.ClassLoaderWrapper.classForName(ClassLoaderWrapper.java:89)&lt;br /&gt;at&amp;nbsp;org.apache.ibatis.io.Resources.classForName(Resources.java:261)&lt;br /&gt;at&amp;nbsp;org.apache.ibatis.type.TypeAliasRegistry.resolveAlias(TypeAliasRegistry.java:116)&lt;br /&gt;...&amp;nbsp;147&amp;nbsp;more&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;당연히 클래스 명이 Test -&amp;gt; TestEnum으로 바꾸었으니 Test라는 클래스를 찾을 수 없다고 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IDE에서 클래스명을 바꾸는 기능을 사용하지 않고 해당 클래스명을 바꾸게 된다면 발생할 수 있을만한 생산성이 떨어지는 일은 매우 많습니다. 이 경우에는 사실 많은 헤딩을 겪게 됩니다. 우선 컴파일 단에서 발생하는 컴파일 에러를 모두 뚫고나면 해당 클래스를 사용하는 기능을 모두 사용해보아서 ClassNotFoundException이 발생할 곳이 없는지 확인해야합니다. 모든 런타임을 확인해야하니까 매우 비효율적인 것 같습니다. 실제로 테스트 코드가 해당 Exception에 대비해서 짜 놓았을 일도 없을 것 같습니다.ㅜㅜ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;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;</description>
      <category>에러모음</category>
      <category>ibatis 에러</category>
      <category>MyBatis 에러</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/453</guid>
      <comments>https://sas-study.tistory.com/453#entry453comment</comments>
      <pubDate>Mon, 29 Aug 2022 11:07:36 +0900</pubDate>
    </item>
    <item>
      <title>[MyBatis] &amp;lt;foreach&amp;gt; 문을 활용한 Bulk Insert</title>
      <link>https://sas-study.tistory.com/452</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;오늘은 MyBatis에서 여러건의 Record를 삽입하기 위해서 쓸 수 있는 &amp;lt;foreach&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;우선 insert 쿼리문을 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1661421362902&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO ROOM(type, deal_type, status) VALUES ('원룸', '월세', '광고중');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 형태로 native 쿼리를 작성해야 할텐데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 처럼 saveAll() 기능을 제공하게 된다면 매우 편하게 삽입작업을 할 수 있겠지만 MyBatis는 쿼리문을 직접 작성하기 때문에 List의 갯수만큼 insert mapper를 호출하거나 해야했을 것입니다.(이렇게 썻다면 정말 안타까운 상황이네요ㅠㅠ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;v1&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1661421642438&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Service {

	public void addRoom(String type, String dealType, String status) {
    	mapper.insertRoom(type, dealType, status);
    }
    
}

public class Mapper {

	int insertRoom(
	    @Param(&quot;type&quot;) String type, 
        @Param(&quot;dealType&quot;) String dealType, 
        @Param(&quot;status&quot;) String status);

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 로직이 있었을때, Service 클래스에서 addRooms라는 여러건의 room을 추가하는 로직이 들어가게 된다면...!&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;b&gt;v2&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1661421761230&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Service {

	public void addRoom(String type, String dealType, String status) {
    	mapper.insertRoom(type, dealType, status);
    }
    
    public void addRooms(List&amp;lt;RoomRequest&amp;gt; requests) {
    	requests.forEach(request -&amp;gt; {
        	mapper.insertRoom(request.getType(), request.getDealType(), request.getStatus());
        });
    }
}

public class Mapper {

	int insertRoom(
	    @Param(&quot;type&quot;) String type, 
        @Param(&quot;dealType&quot;) String dealType, 
        @Param(&quot;status&quot;) String status);

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 이런식으로 불필요하게 mapper를 호출해야 처리가 가능하겠지만&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;b&gt;v3&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1661421932350&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Service {

	public void addRoom(String type, String dealType, String status) {
    	mapper.insertRoom(type, dealType, status);
    }
    
    public void addRooms(List&amp;lt;RoomRequest&amp;gt; requests) {
        mapper.insertRooms(requests);
    }
}

public class Mapper {

	int insertRoom(
	    @Param(&quot;type&quot;) String type, 
        @Param(&quot;dealType&quot;) String dealType, 
        @Param(&quot;status&quot;) String status);

	int insertRooms(@Param(&quot;requests&quot;) List&amp;lt;RoomRequest&amp;gt; requests);
}

-- mapper xml 

&amp;lt;insert id = &quot;insertRoom&quot;&amp;gt;
	INSERT INTO ROOM(type, deal_type, status) VALUES (#{type}, #{dealType}, #{status});
&amp;lt;/insert&amp;gt;

&amp;lt;insert id = &quot;insertRooms&quot;&amp;gt;
	INSERT INTO ROOM(type, deal_type, status) VALUES
	&amp;lt;foreach collection=&quot;requests&quot; item=&quot;email&quot; separator=&quot;,&quot;&amp;gt;
        (
            #{type},
            #{dealType},
            #{status}
        )
    &amp;lt;/foreach&amp;gt;;
&amp;lt;/insert&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는&amp;nbsp; insertRooms 처럼 foreach 태그로 아래와 같은 쿼리를 만들어낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1661422271090&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO ROOM(type, deal_type, status) 
VALUES ('원룸', '월세', '광고중'), ('원룸', '월세', '광고중'), ('원룸', '월세', '광고중');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List의 size() 만큼 record를 삽입할 수 있게된다.&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;b&gt;주의사항&lt;/b&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;여기서 주의할 점은 list의 size가 처리할 수 없는 임계 사이즈를 초과하게 된다면 쿼리를 실행하는 런타임 환경에서 에러를 맞딱뜨릴 수 있으니 해당 size를 고정하는 설정을 처리하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트별 임계 사이즈를 정하여 Bulk Insert 쿼리를 작성할 때 해당 에러가 발생할 수 있음을 인지하고 서비스 로직에 다음과 같은 처리를 하면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1661422801025&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Service {

	public void addRoom(String type, String dealType, String status) {
    	mapper.insertRoom(type, dealType, status);
    }
    
    // TODO 이부분..!
    public void addRooms(List&amp;lt;RoomRequest&amp;gt; requests) {
    	int skip = 0;
        int limit = 500;
        while (skip &amp;lt; requests.size()) {
        	final List&amp;lt;String&amp;gt; perRequests = requests
                            .stream()
                            .skip(skip)
                            .limit(countPerLoop)
                            .collect(toList());
                            
            skip += limit;
            mapper.insertRooms(perRequests); // 500건씩 처리.
        }
    }
}&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;이 부분은 넘어오는 List의 사이즈가 가늠하기 어려운 로직이라면 추가되어야하겠지만 화면단 혹은 bean validation 단계에서 max가 일정 갯수로 고정되어있는 경우라면 굳이 사용할 필요는 없다.&lt;/p&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개의 레코드셋을 가져와서 어떤 Bulk 처리를 할때 활용될 수 있다.&amp;nbsp;&lt;/p&gt;</description>
      <category>DB접근기술/Mybatis</category>
      <category>mybatis bulk insert</category>
      <category>mybatis foreach</category>
      <category>mybatis 반복문 태그</category>
      <author>코딩하는흑구</author>
      <guid isPermaLink="true">https://sas-study.tistory.com/452</guid>
      <comments>https://sas-study.tistory.com/452#entry452comment</comments>
      <pubDate>Thu, 25 Aug 2022 19:22:47 +0900</pubDate>
    </item>
  </channel>
</rss>