<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>bomi-dev.log</title>
        <link>https://velog.io/</link>
        <description>웹 풀스택에서 백엔드로 진화중 🧚🏻‍♀️ </description>
        <lastBuildDate>Thu, 13 Apr 2023 05:19:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>bomi-dev.log</title>
            <url>https://velog.velcdn.com/images/yoon-bomi/profile/8dc47c3e-44b5-47ea-9135-be9475bcb521/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. bomi-dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yoon-bomi" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[nestJS] 정의하지 않은 DTO property 걸러내기]]></title>
            <link>https://velog.io/@yoon-bomi/nestJS-%EC%A0%95%EC%9D%98%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%80-DTO-property-%EA%B1%B8%EB%9F%AC%EB%82%B4%EA%B8%B0</link>
            <guid>https://velog.io/@yoon-bomi/nestJS-%EC%A0%95%EC%9D%98%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%80-DTO-property-%EA%B1%B8%EB%9F%AC%EB%82%B4%EA%B8%B0</guid>
            <pubDate>Thu, 13 Apr 2023 05:19:19 GMT</pubDate>
            <description><![CDATA[<p>nest js 를 사용해 dto 를 검증하다보니 정의하지 않은 property 가 들어왔을 때 어떻게 처리해야할지 궁금해졌다.</p>
<p>찾아보니 Koa joi 의 <code>stripUnKnown</code> 같은 기능을 하는 <code>whitelist</code> 옵션이 있다는 것을 알게 되어 사용법과 사용 결과를 정리해본다.</p>
<br>
<br>
<br>

<h2 id="whitelist-적용-방법">whitelist 적용 방법</h2>
<p>적용 방법은 매우 간단하다. <code>ValidationPipe</code> 의 옵션으로 넣어주면 된다.</p>
<p>매서드 단위로도 작성이 가능하지만 나는 글로벌로 적용했다.</p>
<pre><code class="language-js">  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));</code></pre>
<br>
<br>
<br>


<h2 id="결과">결과</h2>
<ul>
<li><code>whitelist: false</code> 일 경우
<img src="https://velog.velcdn.com/images/yoon-bomi/post/8df3e208-65ea-4e1b-a96d-1283403a9a17/image.png" alt="">
dto 에 정의하지 않은 test property 가 body params 로 함께 들어와버린 것을 확인할 수 있다.</li>
</ul>
<br>



<ul>
<li><code>whitelist: true</code> 일 경우</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/430194a6-97e0-44fa-bd31-4239266f2bfd/image.png" alt="">
dto 에 정의하지 않은 test property 가 제거되어 body params 로 들어온다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[elasticsearch] index mapping]]></title>
            <link>https://velog.io/@yoon-bomi/elasticsearch-index-mapping</link>
            <guid>https://velog.io/@yoon-bomi/elasticsearch-index-mapping</guid>
            <pubDate>Tue, 28 Mar 2023 11:59:52 GMT</pubDate>
            <description><![CDATA[<h1 id="dynamic-mapping">Dynamic mapping</h1>
<ul>
<li><p>별도의 매핑을 지정하지 않았을 경우, 처음 인덱스를 생성하고 데이터를 입력하면 elasticsearch 가 <strong>자동으로 동적 매핑을 진행</strong> → <strong>동적 매핑의 경우 원하는대로 검색이 동작하지 않을 수도 있기 때문에 mapping 을 따로 지정해주는 것을 권장한다. (Explicit mapping)</strong></p>
</li>
<li><p>동적 매핑 시 text 타입은 text 와 keyword 두 가지의 멀티 필드를 생성한다.멀티 필드를 지정할 경우, 필드 개수만큼  용량이 더 소모되므로 keyword 로만 관리할 필드는 keyword 필드, text 로만 관리할 필드는 text 필드로 하는 게 좋다.</p>
</li>
</ul>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-mapping.html">참고</a></p>
<br>

<h1 id="explicit-mapping">Explicit mapping</h1>
<ul>
<li><p>인덱스 생성 시 커스터마이징 해서 지정해주는 매핑</p>
</li>
<li><p><strong>데이터가 들어있을 경우 매핑 변경은 불가능</strong>하기 때문에 인덱스를 지우고 새로 mapping 을 해야한다.</p>
</li>
</ul>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/explicit-mapping.html">참고</a></p>
<br>
<br>
<br>


<h1 id="특정-필드의-매핑-바꾸기">특정 필드의 매핑 바꾸기</h1>
<ol>
<li>인덱스를 만들고 데이터를 하나 넣어서 dynamic mapping 을 생성한다.</li>
<li>해당 mapping 을 불러와서 가공한다.</li>
<li>기존 인덱스를 지우고 다시 인덱스를 생성할 때, 시 가공한 mapping 을 넣어서 explicit mapping 으로 만든다.
<code>PUT http://localhost:1111/haulla-account</code></li>
</ol>
<pre><code>// body
{
  &quot;mappings&quot;: {
    &quot;properties&quot;: {
      &quot;accountManagerId&quot;: {
        &quot;type&quot;: &quot;text&quot;,
        &quot;fields&quot;: {
          &quot;keyword&quot;: {
            &quot;type&quot;: &quot;keyword&quot;,
            &quot;ignore_above&quot;: 256
          }
        }
      },
      ...      
      &quot;role&quot;: {
        &quot;type&quot;: &quot;keyword&quot;, // 타입을 text -&gt; keyword 로 변경
        &quot;fields&quot;: {
          &quot;keyword&quot;: {
            &quot;type&quot;: &quot;keyword&quot;,
            &quot;ignore_above&quot;: 256
          }
        }
      },
     ...
      &quot;vertical&quot;: {
        &quot;type&quot;: &quot;text&quot;,
        &quot;fields&quot;: {
          &quot;keyword&quot;: {
            &quot;type&quot;: &quot;keyword&quot;,
            &quot;ignore_above&quot;: 256
          }
        }
      }
    }
  }
}</code></pre><p><img src="https://velog.velcdn.com/images/yoon-bomi/post/21c61cfa-8fed-4541-9206-14e43c0434dd/image.png" alt=""></p>
<p>새로 만든 role 타입이 keyword 로 mapping 된 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] Objects are not valid as a React child (found: object with keys {key1, key2}).]]></title>
            <link>https://velog.io/@yoon-bomi/React-Objects-are-not-valid-as-a-React-child-found-object-with-keys-key1-key2</link>
            <guid>https://velog.io/@yoon-bomi/React-Objects-are-not-valid-as-a-React-child-found-object-with-keys-key1-key2</guid>
            <pubDate>Thu, 09 Mar 2023 01:57:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/6d615944-5439-4494-90b7-ce6525a9a3ea/image.png" alt=""></p>
<p>리액트로 프론트엔드 화면 작업을 하던 중 위와 같은 에러를 만나게 되었다.</p>
<p>랜더링에 문제가 있다는 내용인 것 같아서 찾아보니 <strong>tsx 에 바로 오브젝트를 때려 넣어서 생긴 문제</strong>였다.</p>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/f670b369-4be2-4e56-acea-e7c7d65284d8/image.png" alt=""></p>
<p><code>plan.collectedWastes</code> 는 <code>{ wasteTypeIdx: number, amount: number }[]</code> 타입의 Object 이기 때문에 바로 랜더링 하도록 적었더니 에러가 난 것이다.</p>
<p>정확하게 어떤 타입인지 랜더링으로 확인하려고 대충 넣은 거였는데 에러가 났다. ㅎ
해당 내용을 제거하니 정상적으로 랜더링이 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[elasticsearch] analyzer 를 이용해 tokens 확인하기]]></title>
            <link>https://velog.io/@yoon-bomi/elasticsearch-analyzer-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-tokens-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yoon-bomi/elasticsearch-analyzer-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-tokens-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 14 Dec 2022 09:36:07 GMT</pubDate>
            <description><![CDATA[<br>

<h1 id="analyzer-구성">analyzer 구성</h1>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/9907a238-a258-46d5-899a-31a3a8b0c96f/image.png" alt=""></p>
<h2 id="텍스트-분석-과정">텍스트 분석 과정</h2>
<ul>
<li><p>애널라이저가 수행</p>
</li>
<li><p>캐릭터 필터링 → 토크나이징 → 토큰 필터링 순서로 진행</p>
</li>
</ul>
<p>토크나이징은 특정 기준으로 분석을 실행해야 해서 1개만 지정 가능하다.</p>
<p>캐릭터 필터는 토크나이징이 진행되기전 캐릭터 전체에 대한 내용을 필터한다.</p>
<p>토큰 필터는 토크나이징이 되고난 후 토큰들에 대한 내용을 필터한다.</p>
<br>
<br>
<br>

<h1 id="analyzer-를-이용해-tokens-확인하기">analyzer 를 이용해 tokens 확인하기</h1>
<ol>
<li><p>로컬에서 elastic 실행</p>
</li>
<li><p><code>GET http://localhost:9200/_analyze</code> 으로 원하는 text 와 analyzer 를 넣어서 요청하기</p>
</li>
</ol>
<pre><code>// body
{
  &quot;text&quot;: [&quot;genorator&quot;, &quot;hauler&quot;],
  &quot;analyzer&quot;: &quot;english&quot;
}</code></pre><p><img src="https://velog.velcdn.com/images/yoon-bomi/post/c04dae45-bc65-428c-890b-5256a763cb33/image.png" alt=""></p>
<p><br><br>
<br></p>
<h1 id="tokenizer-를-이용해-tokens-확인하기">tokenizer 를 이용해 tokens 확인하기</h1>
<ol>
<li><p>로컬에서 elastic 실행</p>
</li>
<li><p><code>GET http://localhost:9200/_analyze</code> 으로 원하는 text 와 tokenizer, filter 를 넣어서 요청하기</p>
</li>
</ol>
<pre><code>// body
{
  &quot;text&quot;: [&quot;genorator&quot;],
  &quot;tokenizer&quot;: &quot;standard&quot;,
  &quot;filter&quot;: [ &quot;stemmer&quot; ]
}</code></pre><p><img src="https://velog.velcdn.com/images/yoon-bomi/post/66c2f174-9195-4257-bdd6-30a61245ad0c/image.png" alt=""></p>
<p>ref.</p>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html#indices-analyze">https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html#indices-analyze</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[elasticsearch] 로컬에서 docker compose 로 엘라스틱 서치 & 키바나 실행하기]]></title>
            <link>https://velog.io/@yoon-bomi/elasticsearch-%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-docker-compose-%EB%A1%9C-%EC%97%98%EB%9D%BC%EC%8A%A4%ED%8B%B1-%EC%84%9C%EC%B9%98-%ED%82%A4%EB%B0%94%EB%82%98-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yoon-bomi/elasticsearch-%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-docker-compose-%EB%A1%9C-%EC%97%98%EB%9D%BC%EC%8A%A4%ED%8B%B1-%EC%84%9C%EC%B9%98-%ED%82%A4%EB%B0%94%EB%82%98-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 22 Nov 2022 05:55:23 GMT</pubDate>
            <description><![CDATA[<p>docker 를 이용하면 로컬에서 간단하게 elaticsearch 와 kibana 를 실행할 수 있다.
<br></p>
<h1 id="docker-compose-파일-작성하기">docker-compose 파일 작성하기</h1>
<pre><code class="language-js">// dokcer-compose.yml
services:
 ma:
    build:
      context: ./my-api
      dockerfile: ../Dockerfile.ma
    environment:
      - NODE_ENV=development
      - PORT=9443
      - DB_NAME=database
      - DB_HOST=db
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=1234
      - ELASTICSEARCH_HOST=http://es:9200 # ELASTICSEARCH_HOST 추가
    ports:
      - &quot;9443:9443&quot;
    depends_on:
      - db
      - es

  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.4.3
    environment:
      - node.name=es
      - discovery.type=single-node # single-node 로 만든다.
      - discovery.seed_hosts=es
      - ELASTIC_PASSWORD=ecubelabs
      - bootstrap.memory_lock=true
      - xpack.security.enabled=false
      - xpack.security.http.ssl.enabled=false
      - xpack.security.http.ssl.verification_mode=certificate
      - xpack.security.transport.ssl.enabled=false
      - xpack.security.transport.ssl.verification_mode=certificate
      - xpack.license.self_generated.type=basic
    mem_limit: 1073741824
    ulimits:
      memlock:
        soft: -1
        hard: -1
    healthcheck:
      test:
        [
          &quot;CMD-SHELL&quot;,
          &quot;curl -s --cacert config/certs/ca/ca.crt http://localhost:9200 | grep -q &#39;missing authentication credentials&#39;&quot;,
        ]
      interval: 10s
      timeout: 10s
      retries: 120
    volumes:
      - es-data:/usr/share/es/data
      - certs:/usr/share/elasticsearch/config/certs
    ports:
      - 9200:9200
  kibana:
    image: docker.elastic.co/kibana/kibana:8.4.3
    environment:
      - ELASTICSEARCH_HOSTS=http://es:9200
    ports:
      - 5601:5601
    depends_on:
      - es

volumes:
  certs:
    driver: local
  es-data:
    driver: local</code></pre>
<br>
my-api 가 없다면 그냥 elasticsearch 와 kibana 만 빌드해도 된다.
kibana 는 elasticsearch 에 들어있는 데이터를 확인하는 용도로 사용할 것이다.

<br>
<br>

<h2 id="security">security?</h2>
<p>로컬에서 실행할 것이기 때문에 security 관련 설정은 <code>false</code> 로 한다.</p>
<ul>
<li><code>xpack.security.enabled=false</code></li>
<li><code>xpack.security.http.ssl.enabled=false</code></li>
<li><code>xpack.security.transport.ssl.enabled=false</code></li>
</ul>
<br>
<br>
<br>


<h1 id="로컬에서-엘라스틱-서치--키바나-실행하기">로컬에서 엘라스틱 서치 &amp; 키바나 실행하기</h1>
<ol>
<li>docker compose up 명령어로 컨테이너 빌드하기<pre><code>docker compose up --build es kibana</code></pre><img src="https://velog.velcdn.com/images/yoon-bomi/post/5cf92a62-a09c-408c-9310-b34195ebe41f/image.png" alt=""></li>
</ol>
<ol start="2">
<li>elasticsearch : <a href="http://localhost:9200/">http://localhost:9200/</a> 으로 접속
<img src="https://velog.velcdn.com/images/yoon-bomi/post/49c22512-4fec-4a05-bdb2-da7759597809/image.png" alt=""></li>
</ol>
<ol start="3">
<li>kibana : <a href="http://localhost:5601/">http://localhost:5601/</a> 으로 접속
<img src="https://velog.velcdn.com/images/yoon-bomi/post/53f426b3-79b7-4a4c-b3de-b82761b9ad14/image.png" alt=""></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[elasticsearch] create index & bulk data]]></title>
            <link>https://velog.io/@yoon-bomi/elasticsearch-create-index-bulk-data</link>
            <guid>https://velog.io/@yoon-bomi/elasticsearch-create-index-bulk-data</guid>
            <pubDate>Mon, 21 Nov 2022 02:58:01 GMT</pubDate>
            <description><![CDATA[<h1 id="인덱스-생성하기">인덱스 생성하기</h1>
<p>만약 이미 생성한 인덱스가 있는데 매핑 정보를 바꾸려 한다면 가장 좋은 방법은 해당 인덱스를 삭제하고 새로 생성하는 것이다. (혹시 모를 인덱스 매핑 오류를 막기 위해 인덱스를 밀고 새로 넣는 게 제일 좋다고 한다.)</p>
<ul>
<li>인덱스에 이미 데이터가 들어있다면 매핑 정보를 수정할 수 없다.</li>
</ul>
<h2 id="포스트맨을-이용해-put-httpes_urlport생성할_인덱스_명칭-에-요청-보내기">포스트맨을 이용해 <code>PUT http://es_url:port/생성할_인덱스_명칭</code> 에 요청 보내기</h2>
<ul>
<li><p>headers : 로컬이 아닌 경우 <code>Authorization - ApiKey</code> 를 함께 넣어서 요청한다.</p>
</li>
<li><p>body</p>
</li>
</ul>
<pre><code>{
  &quot;settings&quot;: {
    &quot;analysis&quot;: {
      &quot;filter&quot;: {
        &quot;english_stop&quot;: {
          &quot;type&quot;:       &quot;stop&quot;,
          &quot;stopwords&quot;:  &quot;_english_&quot; 
        },
        &quot;english_stemmer&quot;: {
          &quot;type&quot;:       &quot;stemmer&quot;,
          &quot;language&quot;:   &quot;english&quot;
        },
        &quot;english_possessive_stemmer&quot;: {
          &quot;type&quot;:       &quot;stemmer&quot;,
          &quot;language&quot;:   &quot;possessive_english&quot;
        }
      },
      &quot;analyzer&quot;: {
        &quot;default&quot;: {
          &quot;tokenizer&quot;: &quot;standard&quot;,
           &quot;filter&quot;: [
            &quot;english_possessive_stemmer&quot;,
            &quot;lowercase&quot;,
            &quot;english_stop&quot;,
            &quot;english_stemmer&quot;
          ],
          &quot;char_filter&quot;: [
            &quot;my_char_filter&quot;
          ]
        }
      },
      &quot;char_filter&quot;: {
        &quot;my_char_filter&quot;: {
          &quot;type&quot;: &quot;pattern_replace&quot;,
          &quot;pattern&quot;: &quot;(\\d+)-(\\d+)-(\\d+)&quot;,
          &quot;replacement&quot;: &quot;$1$2$3&quot;
        }
      }
    }
  }
}</code></pre><p><code>english_analyzer</code> 와 <code>char_filter</code> 를 이용해 커스텀 했다.</p>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lang-analyzer.html#english-analyzer">Language analyzers | Elasticsearch Guide [8.5] | Elastic</a> </p>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-mapping-charfilter.html#analysis-mapping-charfilter">Mapping character filter | Elasticsearch Guide [8.5] | Elastic </a></p>
 <br>
 <br>
<br>

<h1 id="인덱스에-데이터-bulk-하기">인덱스에 데이터 bulk 하기</h1>
<p>많은 양의 데이터를 한 번에 밀어 넣기 위해 bulk API 를 사용한다.</p>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API | Elasticsearch Guide [8.5] | Elastic</a> </p>
<p>이번엔 포스트맨이 아닌 스크립트를 이용해 API 를 호출한다. (인덱스에 저장할 id 를 따로 지정해줘야하기 때문에 스크립트를 사용했고, 그냥 한 개를 입력할 것이라면 포스트맨으로 보내도 된다.)</p>
<ol>
<li><p><code>npm i @elastic/elasticsearch</code></p>
</li>
<li><p>list api 를 호출해서 받은 데이터를 <code>list.json</code> 으로 저장한다.</p>
</li>
<li><p>스크립트에서 <code>list.json</code> 을 import 해서 bulk 데이터로 넣어준다.</p>
</li>
<li><p>아래 스크립트를 만들고 실행한다.</p>
</li>
</ol>
<pre><code class="language-js">// bulk.js
const accounts = require(&quot;./account.json&quot;);
const { Client } = require(&quot;@elastic/elasticsearch&quot;);

const client = new Client({
  node: &quot;요청보낼 es url 입력하면 됩니다. (http, port 포함)&quot;,
  maxRetries: 3,
  auth: {
    apiKey: &quot;security 가 설정되어 있다면 es api key 를 넣어주시면 됩니다.&quot;,
  },
});

const bulk = async (index, data) =&gt; {
  const operations = data.flatMap((doc) =&gt; [
    { index: { _index: index, _id: doc.id } },
    doc,
  ]);

  await client.bulk({ refresh: true, operations });
};</code></pre>
 <br>
 <br>
 <br>


<h1 id="데이터-검색하기">데이터 검색하기</h1>
<p>위에서 bulk 한 데이터를 검색해보자.</p>
<h2 id="포스트맨을-이용해-get-httpes_urlport생성한_인덱스-에-요청-보내기">포스트맨을 이용해 <code>GET http://es_url:port/생성한_인덱스</code> 에 요청 보내기</h2>
<ul>
<li><p>headers : 로컬이 아닌 경우 <code>Authorization - ApiKey</code> 를 함께 넣어서 요청한다.</p>
</li>
<li><p>body</p>
</li>
</ul>
<pre><code> {
 &quot;query&quot;: {
      &quot;bool&quot;: {
        &quot;must&quot;: [
          {
            &quot;multi_match&quot;: {
              &quot;query&quot;: &quot;검색하고 싶은 내용&quot;,
              &quot;type&quot;: &quot;phrase_prefix&quot;
            }
          }
        ]
      }
    }
 }</code></pre><p>검색한 내용에 맞는 결과가 반환되면 성공 👏</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[elasticsearch] What is Elasticsearch?]]></title>
            <link>https://velog.io/@yoon-bomi/elasticsearch-What-is-Elasticsearch</link>
            <guid>https://velog.io/@yoon-bomi/elasticsearch-What-is-Elasticsearch</guid>
            <pubDate>Fri, 18 Nov 2022 01:01:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/4cd4f914-312b-4f59-9160-3d597147ea64/image.png" alt=""></p>
<ul>
<li>Apache Lucene(아파치 루씬) 기반의 java 오픈소스 분산 검색 엔진</li>
<li>방대한 양의 데이터를 신속하고 거의 실시간으로 저장, 검색, 분석 가능</li>
</ul>
<br>

<h3 id="장점">장점</h3>
<ol>
<li>오픈소스 검색 엔진이기 때문에 무료로 사용 가능하다.</li>
<li>역색인을 이용해 방대한 양의 데이터를 신속하게 처리가 가능하다.</li>
</ol>
<br>

<h3 id="단점">단점</h3>
<ol>
<li>진입장벽이 있다.</li>
<li>Document 간의 조인을 수행할 수 없다. (두 번 쿼리로 해결은 가능)</li>
<li>트렌잭션이 제공되지 않는다.</li>
<li>‘실시간(Real Time)&#39; 처리 불가능</li>
<li>진정한 의미의 업데이트를 지원하지 않는다. (실제로는 delete → insert 과정으로 업데이트 됨)</li>
</ol>
<br> 

<h1 id="elasticsearch-vs-rdbms"><strong>Elasticsearch vs RDBMS</strong></h1>
<table>
<thead>
<tr>
<th>Elasticsearch</th>
<th>RDBMS</th>
</tr>
</thead>
<tbody><tr>
<td>mapping</td>
<td>schema</td>
</tr>
<tr>
<td>index</td>
<td>database</td>
</tr>
<tr>
<td>type (deprecated)</td>
<td>table</td>
</tr>
<tr>
<td>document</td>
<td>row</td>
</tr>
<tr>
<td>field</td>
<td>column</td>
</tr>
<tr>
<td>partition</td>
<td>shard</td>
</tr>
</tbody></table>
<p>이 중에 우리가 중점적으로 보아야 하는 것은 <code>index</code>, <code>document</code>, <code>mapping</code> 정도이다.</p>
<br>

<h1 id="elasticsearch-crud"><strong>Elasticsearch</strong> CRUD</h1>
<table>
<thead>
<tr>
<th>Elasticsearch</th>
<th>RDBMS</th>
</tr>
</thead>
<tbody><tr>
<td>스키마 없음</td>
<td>스키마 존재</td>
</tr>
<tr>
<td>역색인 자료 구조</td>
<td>인덱스 자료 구조</td>
</tr>
<tr>
<td>검색 엔진</td>
<td>데이터 저장소</td>
</tr>
</tbody></table>
<br>
<br>


<h2 id="왜-엘라스틱-서치가-더-빠를까"><strong>왜 엘라스틱 서치가 더 빠를까?</strong></h2>
<ul>
<li>데이터를 저장하고 있는 구조 자체가 다르다. (역색인 구조)
<img src="https://velog.velcdn.com/images/yoon-bomi/post/0697974c-0a11-4d37-bfca-0c847902eb7b/image.jpeg" alt="">
<img src="https://velog.velcdn.com/images/yoon-bomi/post/cd6dd21e-0707-41e8-9b5c-89d9b1e8f4ca/image.png" alt=""></li>
</ul>
<p>좌 : 색인 구조(문서에서 키워드를 찾아 보기 쉽도록 정렬 및 나열한 목록)</p>
<p>우 : 역색인 구조(키워드를 통해 문서를 찾아내는 방식)</p>
<br>


<h2 id="그럼-엘라스틱-서치를-쓸-일이-뭐가-있을까">그럼 엘라스틱 서치를 쓸 일이 뭐가 있을까?</h2>
<ol>
<li>관계형 데이터베이스는 단순 텍스트 매칭에 대한 검색만을 제공<ul>
<li>MySQL 최신 버전에서 n-gram 기반의 Full-text 검색을 지원하지만, 한글 검색의 경우에 아직 많이 빈약하다고 한다.</li>
</ul>
</li>
<li>텍스트를 여러 단어로 변형하거나 텍스트의 특질을 이용한 동의어나 유의어를 활용한 검색이 가능</li>
<li>엘라스틱서치에서는 관계형 데이터베이스에서 불가능한 비정형 데이터의 색인과 검색이 가능</li>
<li>엘라스틱서치에서는 형태소 분석을 통한 자연어 처리가 가능<ul>
<li>엘라스틱서치는 다양한 형태소 분석 플러그인을 제공</li>
</ul>
</li>
<li>역색인 지원으로 매우 빠른 검색이 가능</li>
</ol>
<br>
<br>
<br>
<br>


<p>ref.
<a href="https://velog.io/@jakeseo_me/%EC%97%98%EB%9D%BC%EC%8A%A4%ED%8B%B1%EC%84%9C%EC%B9%98-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-2-DB%EB%A7%8C-%EC%9E%88%EC%9C%BC%EB%A9%B4-%EB%90%98%EB%8A%94%EB%8D%B0-%EC%99%9C-%EA%B5%B3%EC%9D%B4-%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84">https://velog.io/@jakeseo_me/%EC%97%98%EB%9D%BC%EC%8A%A4%ED%8B%B1%EC%84%9C%EC%B9%98-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-2-DB%EB%A7%8C-%EC%9E%88%EC%9C%BC%EB%A9%B4-%EB%90%98%EB%8A%94%EB%8D%B0-%EC%99%9C-%EA%B5%B3%EC%9D%B4-%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84</a>
<a href="https://choseongho93.tistory.com/231">https://choseongho93.tistory.com/231</a>
<a href="https://sudarlife.tistory.com/entry/Elasticsearch-%EA%B0%84%EB%8B%A8-%EA%B0%9C%EB%85%90-%EC%9E%A5%EB%8B%A8">https://sudarlife.tistory.com/entry/Elasticsearch-%EA%B0%84%EB%8B%A8-%EA%B0%9C%EB%85%90-%EC%9E%A5%EB%8B%A8</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[node]firebase 를 이용해 푸시 알림 보내기 with node.js]]></title>
            <link>https://velog.io/@yoon-bomi/nodefirebase-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%ED%91%B8%EC%8B%9C-%EC%95%8C%EB%A6%BC-%EB%B3%B4%EB%82%B4%EA%B8%B0-with-node.js</link>
            <guid>https://velog.io/@yoon-bomi/nodefirebase-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%ED%91%B8%EC%8B%9C-%EC%95%8C%EB%A6%BC-%EB%B3%B4%EB%82%B4%EA%B8%B0-with-node.js</guid>
            <pubDate>Sun, 18 Sep 2022 14:11:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🚀 프론트앤드에서 유저의 디바이스 토큰을 보내준다는 가정하에 진행한다.</p>
</blockquote>
<p><a href="https://firebase.google.com/docs/cloud-messaging/server?hl=ko">firebase 공식문서</a>에 정리가 잘 되어있어서 한 번 그대로 진행해본다.</p>
<br> 


<h3 id="1-프로젝트에서-sdk-초기화를-위한-서버-키-발급받기">1. 프로젝트에서 SDK 초기화를 위한 서버 키 발급받기</h3>
<p><a href="https://console.firebase.google.com/">firebase 콘솔</a>에서 <code>내 프로젝트 &gt; 설정 &gt; 프로젝트 설정</code>으로 들어간다.
<img src="https://velog.velcdn.com/images/yoon-bomi/post/7d100a8d-6e12-4990-af19-7c8b40585b87/image.png" alt=""></p>
<p><code>서비스 계정 &gt; 새 비공개 키</code> 생성 버튼 클릭
<img src="https://velog.velcdn.com/images/yoon-bomi/post/49476bec-c4aa-4b96-94d7-1b7cee3270e0/image.png" alt=""></p>
<p><code>키 생성</code> 버튼 클릭
<img src="https://velog.velcdn.com/images/yoon-bomi/post/76bec013-66d8-4de3-8d2c-7d046634c509/image.png" alt=""></p>
<p>그러면 내 로컬에 서버 키 파일.json 이 생긴다.
이 파일은 추후 재발급이 불가능하다고 하니 잘 보관해준다. (프로젝트에 넣을 예정)</p>
<br>


<h3 id="2-firebase-admin-sdk-설치하기">2. firebase-admin SDK 설치하기</h3>
<p>firebase-admin 패키지를 설치해준다.</p>
<pre><code class="language-js">npm i firebase-admin</code></pre>
<br>

<h3 id="3-sdk-초기화">3. SDK 초기화</h3>
<p>먼저 다운받은 서버 키를 프로젝트 안에 넣어준다.
나는 <code>configs</code> 폴더 안에 <code>firebase-admin.json</code> 이라는 이름으로 넣었다.</p>
<pre><code class="language-js">import admin from &#39;firebase-admin&#39;;

const serviceAccount = require(&#39;./configs/firebase-admin.json&#39;);

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
});</code></pre>
<p>새 비공개 키를 만들 때 적혀있던 코드 스니펫을 이용하여 서버 키를 import 해주고 서버 키를 이용해 credential 설정을 해준다.</p>
<br>


<h3 id="4-푸시알림-생성하기">4. 푸시알림 생성하기</h3>
<pre><code class="language-js"> async sendPushNoti(email: string) {
    const targetMember = await this.memberService.findMemberByEmail(email);

    const message = {
      notification: {
        title: &#39;테스트 제목입니다.&#39;, 
        body: &#39;푸시알림 내용입니다.&#39;,
      },
      token: targetMember.fcmToken, // user 의 device token 을 넣어준다.
    };

    admin
      .messaging()
      .send(message)
      .then(function (response) {
        console.log(&#39;Successfully sent message: : &#39;, response)
      })
      .catch(function (err) {
        console.log(&#39;Error Sending message!!! : &#39;, err)
      })
  }</code></pre>
<p>유저 email 으로 DB 에 저장되어있는 유저의 디바이스 토큰을 이용해 푸시 알림을 생성한다.</p>
<br>


<h3 id="5-테스트-결과">5. 테스트 결과</h3>
<p>ios 기준 정상적으로 푸시 알림이 뜨는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/yoon-bomi/post/8d9f4d99-8d80-4297-b747-01971b97ba1b/image.JPG" alt=""></p>
<br>
<br>

<h3 id="참고">참고</h3>
<p>해당 내용은 앱이 화면에 떠있지 않은 상태, 즉 백그라운드 상태일 때만 heads up 알림이 생성된다. 앱이 실행 중일 때(포그라운드) 알림을 뜨게 하려면 <a href="https://firebase.google.com/docs/cloud-messaging/concept-options#notification-messages-with-optional-data-payload">공식 문서</a>에 적힌 것처럼 data 라는 key 에 정보를 담아서 프론트에 보내주고 콜백으로 해당 메세지를 받아야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ERROR]EntityMetadataNotFoundError: No metadata for "File" was found.]]></title>
            <link>https://velog.io/@yoon-bomi/ERROREntityMetadataNotFoundError-No-metadata-for-File-was-found</link>
            <guid>https://velog.io/@yoon-bomi/ERROREntityMetadataNotFoundError-No-metadata-for-File-was-found</guid>
            <pubDate>Tue, 06 Sep 2022 13:48:01 GMT</pubDate>
            <description><![CDATA[<h3 id="원인">원인</h3>
<p>typeORM 버전을 0.2.x -&gt; 0.3.x 로 업그레이드 하면서 발생한 에러이다.
버전이 바뀌면서 DB connection 에 관한 방법이 변경되었는데, <code>initialize()</code> 로 DB connection pool 을 오픈하지 않아서 발생한 에러였다. 😅</p>
<br>

<h3 id="typeorm-02x-version">typeORM 0.2.x version</h3>
<pre><code class="language-js"> const conn = await createConnection({
        ...ormconfig,
        entities,
    });

    await Promise.all([conn.connect()]);</code></pre>
<br>

<h3 id="typeorm-03x-version">typeORM 0.3.x version</h3>
<pre><code class="language-js">    const conn = new DataSource({
        ...ormconfig,
        entities,
    });

    await Promise.all([conn.initialize()]); // opens connection pool to the database</code></pre>
<br>
<br>

<p>ref. <a href="https://typeorm.biunav.com/en/data-source-api.html#datasource-api">https://typeorm.biunav.com/en/data-source-api.html#datasource-api</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] What is Localstack?]]></title>
            <link>https://velog.io/@yoon-bomi/AWS-What-is-Localstack</link>
            <guid>https://velog.io/@yoon-bomi/AWS-What-is-Localstack</guid>
            <pubDate>Tue, 23 Aug 2022 01:51:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>로컬에서 S3 에 파일 업로드를 테스트 하던 중, localStack 을 사용하면 된다고 해서 알아보고 직접 적용해본 내용을 정리했다.</p>
</blockquote>
<br>

<h1 id="공식문서">공식문서</h1>
<p><a href="https://github.com/localstack/localstack">https://github.com/localstack/localstack</a></p>
<h1 id="what-is-localstack-">What is Localstack ?</h1>
<ul>
<li>AWS API 를 시뮬레이션 해주는 프레임워크</li>
<li><strong>도커 컨테이너 형태</strong>로 제공되며, AWS REST API 스펙에 맞는 모킹 http 서버를 실행한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/b10e9b67-6e51-4d8e-bbf9-93a69309658a/image.png" alt=""></p>
<br>

<h1 id="localstack-지원하는-서비스">Localstack 지원하는 서비스</h1>
<p>Localstack은 유료 버전과 무료 버전으로 나눠져 있으며 무료로 제공하는 서비스는 아래와 같다.</p>
<pre><code>Note: localstack 0.11.0 부터는 모든 APIs 단위 포인트(http://localhost:4566)로 연결을 됨API Gateway at http://localhost:4567
Kinesis at http://localhost:4568
DynamoDB at http://localhost:4569
DynamoDB Streams at http://localhost:4570
S3 at http://localhost:4572
Firehose at http://localhost:4573
Lambda at http://localhost:4574
SNS at http://localhost:4575
SQS at http://localhost:4576
Redshift at http://localhost:4577
Elasticsearch Service at http://localhost:4578
SES at http://localhost:4579
Route53 at http://localhost:4580
CloudFormation at http://localhost:4581
CloudWatch at http://localhost:4582
SSM at http://localhost:4583
SecretsManager at http://localhost:4584
StepFunctions at http://localhost:4585
CloudWatch Logs at http://localhost:4586
EventBridge (CloudWatch Events) at http://localhost:4587
STS at http://localhost:4592
IAM at http://localhost:4593
EC2 at http://localhost:4597
KMS at http://localhost:4599
ACM at http://localhost:4619</code></pre><br>

<h1 id="왜-쓰나요">왜 쓰나요?</h1>
<ul>
<li>실제 aws s3 와 연동하지 않고도 로컬에서 aws 서비스를 테스트 할 수 있다.</li>
<li>AWS 서비스를 사용하는 인프라 로직의 통합테스트를 작성하기 위해 사용한다.</li>
</ul>
<br>


<h1 id="localstack-을-실행하는-방법">localstack 을 실행하는 방법</h1>
<ol>
<li>Testcontainers</li>
<li><strong>docker compose</strong></li>
</ol>
<p>→ docker compose 를 사용하는 방법으로 테스트해보겠다.</p>
<br>


<h1 id="docker-compose-로-localstack-실행하기">docker compose 로 localstack 실행하기</h1>
<ul>
<li><p>docker-compose.yml</p>
<pre><code>aws:
  image: localstack/localstack
  environment:
    - SERVICES=s3 // 사용할 서비스 , 를 이용해 나열
    - DEBUG=1
  volumes:
    - &quot;./test/docker-entrypoint-initaws.d:/docker-entrypoint-initaws.d&quot;</code></pre></li>
<li><p>사용할 서비스는 <code>SERVICES=ses,s3...</code> 처럼 나열이 가능하다.</p>
</li>
<li><p><code>./test/docker-entrypoint-initaws.d</code> 경로에 있는 파일에 s3 버킷을 만들 수 있다.</p>
</li>
</ul>
<br>

<ul>
<li>./test/docker-entrypoint-initaws.d<pre><code>#!/bin/sh
</code></pre></li>
</ul>
<p>awslocal s3 mb s3://test</p>
<pre><code>
- awslocal 명령어를 이용해 test 버킷을 만들었다.

&lt;br&gt;

docker compose up 명령어를 이용해 빌드 후 실행 시켜준다.
![test](https://velog.velcdn.com/images/yoon-bomi/post/3fa6daac-3b09-4d73-bb54-3c5e3e8a9b01/image.png)

make bucket test 가 제대로 뜬다면 성공 🚀

&lt;br&gt;

</code></pre><p>str:
    build:
      context: ./storage-api
      dockerfile: ../Dockerfile.str
    environment:
      - NODE_ENV=development
      - DB_HOST=db
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=1234
      - PORT=3030
      - AWS_ENDPOINT=<a href="http://aws:4566">http://aws:4566</a> # see <a href="https://github.com/localstack/localstack">https://github.com/localstack/localstack</a>
      - AWS_ACCESS_KEY_ID=aws # 아무 값이나 환경변수로 지정하기만 하면 ok
      - AWS_SECRET_ACCESS_KEY=aws # 아무 값이나 환경변수로 지정하기만 하면 ok
    depends_on:
      - db
    ports:
      - &quot;3030:3030&quot;</p>
<p>```</p>
<ul>
<li>사용하고자 하는 도커에서 <code>AWS_ENDPOINT=http://aws:4566</code> 로 엔드포인트를 설정해준다. (그래야 localstack 으로 요청이 간다.)</li>
<li><code>AWS_ACCESS_KEY_ID</code>, <code>AWS_SECRET_ACCESS_KEY</code> 는 아무 값이나 넣어준다.</li>
</ul>
<br>


<h2 id="storage-api-를-이용한-localstack-post-요청-테스트-파일-업로드">storage API 를 이용한 localstack POST 요청 테스트 (파일 업로드)</h2>
<p>특정 프로젝트에서 파일 서버를 통해 S3로 파일이 업로드 되는 과정을 테스트한다.
실제 S3와 연결하지 않고 localstack 을 이용해 보았다.</p>
<ul>
<li>포스트맨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/6aef6294-726a-40f9-989b-be992bbdfdf5/image.png" alt=""></p>
<ul>
<li>localstack docker</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/984b197d-38e8-488b-a8c2-bf18b6140fa2/image.png" alt=""></p>
<ul>
<li>결과 : 성공적으로 업로드 된 것을 확인할 수 있다.</li>
</ul>
<br>
localstack 을 통해 업로드 된 파일은 메모리에 저장된다고 한다.
그래서 가시적으로 확인할 수 있는 방법이 없다고 하는데 .. 🤔 스텍오버플로우를 보면 특정 툴을 이용해 확인이 가능한 것도 같다. (유료인지 무료인지는 모름)

<br>
<br>



<p>ref.</p>
<p><a href="https://tech.inflab.com/202202-integration-test-with-localstack/">https://tech.inflab.com/202202-integration-test-with-localstack/</a>
<a href="https://medium.com/@dudwls96/localstack-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0-9b81ec51749c">https://medium.com/@dudwls96/localstack-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0-9b81ec51749c</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redis] What is Redis?]]></title>
            <link>https://velog.io/@yoon-bomi/Redis-What-is-Redis</link>
            <guid>https://velog.io/@yoon-bomi/Redis-What-is-Redis</guid>
            <pubDate>Sat, 20 Aug 2022 05:47:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>💡 [[NHN FORWARD 2021] Redis 야무지게 사용하기] (<a href="https://www.youtube.com/watch?v=92NizoBL4uA">https://www.youtube.com/watch?v=92NizoBL4uA</a>) 를 시청하며 정리한 내용입니다.</p>
</blockquote>
<br>
<br>


<h1 id="what-is-redis">What is Redis?</h1>
<blockquote>
<p>전세계에서 가장 유명한 Caching 솔루션
대부분 cache 용도로 Redis 를 사용한다.</p>
</blockquote>
<blockquote>
<ul>
<li>Redis 좀 더 알아보기
   Remote dictionary server (외부 HashMap 서버)
   Single Thread 서버이다. → 시간 복잡도 고려 필요 (O(N) X)</li>
</ul>
</blockquote>
<h1 id="1-redis-를-cache-로-사용하는-방법">1. Redis 를 cache 로 사용하는 방법</h1>
<h2 id="cache-">cache ?</h2>
<ul>
<li>cache 란 사용자의 입장에서 데이터의 원래 소스보다 더 빠르고 효율적으로 엑세스 할 수 있는 <strong>임시 데이터 저장소</strong>를 뜻한다.</li>
<li>대부분의 어플리케이션에서 <strong>속도 향상</strong>을 위해 cache 를 사용한다. 따라서 cache 에 접근하는 것이 원본에 접근하는 것보다 쉽고 빨라야 한다.</li>
</ul>
<p>Q. cache 는 언제 사용하는 것이 좋을까?</p>
<p>A. <strong>동일한 데이터에 대해 반복적으로 액세스 하는 상황</strong>이 많을 때 !</p>
<h3 id="의미있는-cache"><strong>의미있는</strong> cache</h3>
<ol>
<li><strong>데이터의 재사용 횟수가 한 번 이상일 경우</strong></li>
<li><strong>잘 변하지 않는 데이터</strong>일수록 cache 를 사용할 때 더 효율적</li>
</ol>
<h2 id="redis-as-a-cache">Redis as a cache</h2>
<ul>
<li><p>단순한 key-value 구조 : 사용이 간편하고 데이터 저장이 쉽다.</p>
</li>
<li><p><strong>in-memory</strong> 데이터 저장소 (RAM)</p>
<ul>
<li>빠른 성능 : 평균 작업 속도 &lt; 1 ms, 초당 수백만 건의 작업 가능</li>
</ul>
</li>
</ul>
<h2 id="캐싱-전략caching-strategies">캐싱 전략(Caching Strategies)</h2>
<p>데이터의 유형과 해당 데이터에 대한 액세스 패턴을 잘 고려해서 선택해야 한다.</p>
<h3 id="1-look-aside">1. look-aside</h3>
<p>데이터를 읽는 작업이 많을 때 사용한다. (가장 일반적으로 사용하는 방법)</p>
<p>1️⃣ 어플리케이션이 데이터를 찾을 때 cache 에 먼저 확인한다.</p>
<p>2️⃣ cache 에 데이터가 있을 경우, cache 에서 데이터를 가지고 오는 작업을 반복한다.</p>
<p>3️⃣ 만약 redis 에 찾는 키가 없다면 어플리케이션은 DB 에 접근해서 데이터를 가져온 뒤 다시 redis 에 저장하는 과정을 거친다.</p>
<p>⇒ cache 는 찾는 데이터가 없을 때에만 입력되기 때문에 Lazy Loading (지연로딩) 이라고도 부른다.</p>
<table>
<thead>
<tr>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>• 이 구조는 redis 가 다운되더라도 바로 장애로 이어지지 않고 DB 에서 데이터를 가져올 수 있다.</td>
<td>• cache 로 붙어있던 커넥션이 많이 있었다면, 그 커넥션이 모두 DB 에 붙기 때문에 DB 에 갑자기 많은 부하가 몰릴 수 있다.</td>
</tr>
<tr>
<td></td>
<td>• 이런 경우에 cache 를 새로 투입하거나 DB 에만 새로운 데이터를 저장했다면 처음에 캐시 미스가 엄청 발생해서 성능에 저하가 올 수 있다.</td>
</tr>
</tbody></table>
<p>⇒ DB 에서 cache 로 데이터를 밀어 넣어주는 작업 : <strong>Cache Warming</strong> 을 통해 방지 가능</p>
<p>ex) 티켓링크에서 상품 오픈 전 상품의 정보를 미리 DB 에서 cache 로 올려주는 작업을 매번 한다.</p>
<h3 id="2-write-around">2. write-around</h3>
<p>DB 에만 데이터를 저장한다.</p>
<p>1️⃣ 모든 데이터를 DB 에 저장한다.</p>
<p>2️⃣ 캐시 미스가 발생한 경우에만 DB 에서 데이터를 꺼내 cache 로 끌어 온다.</p>
<table>
<thead>
<tr>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>?</td>
<td>• cache 내의 데이터와 DB 내의 데이터가 다를 수 있다.</td>
</tr>
</tbody></table>
<h3 id="3-write-through">3. write-through</h3>
<p>DB 에 데이터를 저장할 때, cache 에도 함께 저장한다.</p>
<table>
<thead>
<tr>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>• cache 에 항상 최신 데이터가 담겨있다.</td>
<td>• 저장할때마다 두 단계를 거쳐야하기 때문에 상대적으로 느리다.</td>
</tr>
<tr>
<td></td>
<td>• 저장하는 데이터가 재사용되지 않을 수도 있는데 무조건 캐시에 넣어버려서 일종의 리소스 낭비가 될 수 있다.</td>
</tr>
<tr>
<td>- 따라서, 데이터를 저장할 때에는 <strong>expire time</strong> 을 설정해주는 것이 좋다.</td>
<td></td>
</tr>
</tbody></table>
<blockquote>
<p><strong>expire time ?</strong>
     몇 분, 혹은 몇 시간동안만 데이터를 보관하겠다는 의미</p>
</blockquote>
<h1 id="2-redis-데이터-타입">2. Redis 데이터 타입</h1>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/31363e7b-6e93-4e76-bc74-2313d6e7db29/image.png" alt=""></p>
<ul>
<li>Strings : set 커맨드를 이용해 저장된 데이터는 모두 String 형태로 들어간다.</li>
<li>Bitmaps : string 의 변형, bit 단위의 연산이 가능하다.</li>
<li>Lists : 데이터를 순서대로 저장 == 큐로 사용하기 적절하다.</li>
<li>Hashes : 하나의 키 안에 또다시 여러 개의 필드와 벨류 쌍으로 데이터를 저장한다.</li>
<li>Sets : 중복되지 않은 문자열의 집합</li>
<li>Sorted Sets : set 처럼 중복되지 않은 모든 값을 score 라는 숫자 값으로 저장한다. 저장될 때부터 score 순으로 정렬되며, score 가 같을 때에는 사전순으로 정렬된다.</li>
<li>HyperLogLogs : 굉장히 많은 데이터를 다룰 때 주로 쓰며 중복되지 않는 값의 개수를 카운트할 때 사용한다. → 먼소리임?</li>
<li>Streams : log 를 저장하기 가장 좋은 자료구조이다.</li>
</ul>
<h2 id="best-practice---counting">Best Practice - Counting</h2>
<table>
<thead>
<tr>
<th>Strings</th>
<th>Bits</th>
<th>HyperLogLogs</th>
</tr>
</thead>
<tbody><tr>
<td>• 단순 증감 연산</td>
<td>• 데이터 저장공간 절약</td>
<td>• 모든 string 데이터 값을 유니크하게 구분할 수 있다.</td>
</tr>
<tr>
<td>• INCR (increment) / INCRBY (increment by)</td>
<td>• 정수로 된 데이터만 카운팅 가능</td>
<td>• 대용량의 데이터를 카운팅 할 때 적절</td>
</tr>
<tr>
<td></td>
<td></td>
<td>• 저장되는 데이터 개수에 상관없이 모두 12KB 로 고정 → 용량이 매우 작음</td>
</tr>
<tr>
<td></td>
<td></td>
<td>• 한 번 저장된 데이터는 다시 불러올 수 없다. (경우에 따라 데이터 보호 가능) ex) 검색 엔진에서 검색된 유니크한 단어가 몇 개인지</td>
</tr>
</tbody></table>
<h2 id="best-practice---messaging">Best Practice - Messaging</h2>
<table>
<thead>
<tr>
<th>Lists</th>
<th>Streams</th>
</tr>
</thead>
<tbody><tr>
<td>• 메세지 큐로 사용하기 적절</td>
<td>• 로그를 저장하기 가장 적절한 자료구조</td>
</tr>
<tr>
<td>• 자체적인 Blocking 기능 → 불필요한 polling 을 막을 수 있다, Event Queue 로 사용</td>
<td>• 실제 서버에 로그가 쌓이는 것처럼 append-only 방식으로 저장, 중간에 데이터가 바뀌지 않는다.</td>
</tr>
<tr>
<td></td>
<td>• 카프카의 개념을 많이 차용</td>
</tr>
</tbody></table>
<p>ex) 인스타, 페이스북, 트위터에는 유저별로 타임라인이 존재하고, 그 타임라인에는 내가 팔로우한 사람들의 데이터가 뜬다. 트위터에서는 각 유저의 타임라인에 보일 트윗을 캐싱하기 위해 redis 의 list 를 사용하는데, 이 때 RPUSHX 커맨드를 사용한다. 이를 이용해 트위터를 자주 이용하던 유저의 타임라인에만 새로운 데이터를 미리 캐시해 놓을 수 있으며 자주 사용하지 않는 유저는 caching key 자체가 존재하지 않기 때문에 이 유저들을 위한 데이터를 미리 쌓아두는 것과 같은 비효율적인 작업을 방지할 수 있게 된다.</p>
<h1 id="3-redis-에서-데이터를-영구-저장하기rdb-vs-aof">3. Redis 에서 데이터를 영구 저장하기(RDB vs AOF)</h1>
<h2 id="redis-는-in-memory-데이터-스토어">Redis 는 in-memory 데이터 스토어</h2>
<ul>
<li>모든 데이터가 메모리에 저장되어있기 때문에, 서버나 redis 인스턴스가 재시작되면 모든 데이터 유실</li>
<li>복제 구조로 되어있어도 데이터 유실에서 안전하지 않다.</li>
</ul>
<h2 id="redis-persistence-option">Redis Persistence Option</h2>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/11267d13-69ca-4677-a3b1-1b56d2ed6d54/image.png" alt=""></p>
<h3 id="1-aof-append-only-file">1. AOF (Append Only File)</h3>
<ul>
<li>데이터를 변경하는 커멘드가 들어오면 커맨드를 그대로 모두 저장한다. (redis 프로토콜 형태로 저장)</li>
<li>데이터가 추가되기만 해서 대부분 RDB 파일보다 사이즈가 커진다. → 주기적으로 압축해서 재작성되는 과정을 거쳐야 한다.</li>
</ul>
<h3 id="2-rdb">2. RDB</h3>
<ul>
<li>스냅샷 방식으로 동작 → 저장 당시의 메모리에 있는 데이터를 그대로 스냅샷으로 찍어서 저장한다. (바이너리 파일 형태로 저장)</li>
</ul>
<blockquote>
<p>AOF 와 RDB 모두 커맨드를 이용해 직접 파일을 생성하거나 원하는 시점에 자동으로 파일이 생성되도록 할 수 있다.</p>
</blockquote>
<table>
<thead>
<tr>
<th></th>
<th>자동</th>
<th>수동</th>
</tr>
</thead>
<tbody><tr>
<td>AOF</td>
<td>redis.conf 파일에서 auto-aof-rewrite-percentage 옵션(크기 기준)</td>
<td>BGREWRITEAOF 커맨드 이용 → CLI 창에서 수동으로 AOF 파일 재작성</td>
</tr>
<tr>
<td>RDB</td>
<td>redis.conf 파일에서 SAVE 옵션(시간 기준)</td>
<td>BGSAVE 커맨드 이용 → CLI 창에서 수동으로 RDB 파일 저장 (SAVE 커맨드는 절대 사용 X)</td>
</tr>
</tbody></table>
<h2 id="aof-vs-rdb-선택-기준">AOF vs RDB 선택 기준</h2>
<ul>
<li>우선 redis 를 캐시로만 사용한다면, 둘 다 쓸 필요가 없다.</li>
</ul>
<h3 id="1-백업은-필요하지만-어느-정도의-데이터-손실이-발생해도-괜찮은-경우">1. 백업은 필요하지만 어느 정도의 데이터 손실이 발생해도 괜찮은 경우</h3>
<ul>
<li>RDB 단독 사용</li>
<li>redis.conf 파일에서 SAVE 옵션을 적절히 사용
ex) SAVE 900 1 → 900 초 동안 한 개 이상의 키가 변경되었을 때 RDB 파일을 재작성해라.</li>
</ul>
<h3 id="2-장애-상황-직전까지의-모든-데이터가-보장되어야-할-경우">2. 장애 상황 직전까지의 모든 데이터가 보장되어야 할 경우</h3>
<ul>
<li>AOF 사용</li>
<li>APPENDFSYNC 옵션이 everysec 인 경우 최대 1초 사이의 데이터 유실 가능(기본 설정)</li>
</ul>
<h3 id="3-제일-강력한-내구성이-필요한-경우">3. 제일 강력한 내구성이 필요한 경우</h3>
<ul>
<li>RDB &amp; AOF 동시 사용</li>
</ul>
<h1 id="4-redis-아키텍처">4. Redis 아키텍처</h1>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/e0c8920c-1eee-4de7-8469-f6c1e2fcf72f/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>Replication (복제)</th>
<th>Sentinel</th>
<th>Cluster</th>
</tr>
</thead>
<tbody><tr>
<td>• 마스터와 리플리카만 존재하는 간단한 구조</td>
<td>• 마스터와 리플리카 노드 외에 센티널 노드가 필요하다.</td>
<td>• 센티널은 일반 노드들을 모니터링하는 역할을 한다.</td>
</tr>
<tr>
<td></td>
<td>• 최소 세대의 마스터가 필요하며 샤딩 기능을 제공한다.</td>
<td></td>
</tr>
</tbody></table>
<h3 id="1-replication-구성">1. Replication 구성</h3>
<ul>
<li>단순한 복제 연결</li>
<li>모든 redis 의 구조에서 복제는 비동기식으로 동작한다. → 마스터에서 복제본에 데이터가 잘 전달됐는지 매번 확인하고 기다리지 않는다.</li>
<li>이 구조는 HA (High Availability) 기능이 없기 때문에 마스터에 장애가 발생하면 수동으로 변경해줘야 하는 작업들이 많다.
(리플리카 노드에 직접 접속해서 복제를 끊어야 하고, 어플리케이션에서도 연결 설정을 변경하여 배포하는 작업이 필요하다.)</li>
</ul>
<blockquote>
<ul>
<li>HA (High Availability) 기능 ?
  <strong>자동 페일오버(장애 극복 기능)</strong></li>
</ul>
</blockquote>
<h3 id="2-sentienl-구성">2. Sentienl 구성</h3>
<ul>
<li>자동 페일오버 가능한 HA 구성(High Availability)</li>
<li>센티널 노드는 일반적인 다른 노드를 계속 모니터링 → 마스터가 죽으면 자동으로 페일오버를 발생시켜 기존의 리플리카 노드가 마스터가 된다. 이때 어플리케이션에서는 연결 정보를 변경할 필요가 없다. 어플리케이션은 센티널 노드만 알고 있으면 되고, 센티널이 변경된 마스터 정보로 바로 연결시켜 준다.</li>
<li>이 구조를 사용하기 위해서는 센티널 프로세스를 추가로 띄워야 하는데, 센티널은 항상 세대 이상의 홀수로 존재해야 한다.</li>
</ul>
<h3 id="3-cluster-구성">3. Cluster 구성</h3>
<ul>
<li>스케일 아웃과 HA 구성 (High Availability)</li>
<li>데이터가 여러 마스터 노드에 자동으로 분할되어 저장되는 샤딩 기능(스케일 아웃)을 제공한다.</li>
<li>모든 노드가 서로 감시하고 있다가 마스터가 비정상 상태일 때 자동으로 페일오버를 진행한다.</li>
<li>최소 세대 이상의 마스터 노드가 필요하고, 리플리카 노드를 하나씩 추가하는 게 일반적인 구조이다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/b6c274b7-4840-4821-b39d-6bc409fd0bf5/image.png" alt=""></p>
<h1 id="5-redis-운영-팁과-장애-포인트">5. Redis 운영 팁과 장애 포인트</h1>
<p>redis 는 싱글 스레드로 동작한다.</p>
<p>한 사용자가 오래 걸리는 커맨드를 실행한다면 병목현상이 발생할 수 있다.</p>
<h3 id="사용하면-안되는-커맨드">사용하면 안되는 커맨드</h3>
<ul>
<li>keys → scan 으로 대체</li>
<li>Hash 나 Sorted Set 등 자료구조<ul>
<li>hgetall → hscan</li>
<li>del → unlink</li>
</ul>
</li>
</ul>
<h3 id="변경하면-장애를-막을-수-있는-기본-설정값">변경하면 장애를 막을 수 있는 기본 설정값</h3>
<ul>
<li><p>STOP-WRITES-ON-BGSAVE-ERROR = NO</p>
<ul>
<li>RDB 파일 저장 실패 시 redis 로의 모든 write 불가능</li>
</ul>
</li>
<li><p>MAXMEMORY-POLICY = ALLKEYS-LRU</p>
<ul>
<li>redis 를 캐시로 사용할 때 expire time 설정 권장</li>
<li>메모리가 가득 찼을 때 MAXMEMORY-POLICY 정책에 의해 키 관리 : ALLKEYS-LRU → 모든 key 에 대해 lru 방식(가장 최근에 사용하지 않았던 key 부터 삭제한다)으로 key 를 삭제하겠다는 것을 의미</li>
</ul>
</li>
</ul>
<br>
<br>
<br>


<p>ref. <a href="https://www.youtube.com/watch?v=92NizoBL4uA">https://www.youtube.com/watch?v=92NizoBL4uA</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[javascript] isNaN() vs Nember.isNaN() 뿌시기]]></title>
            <link>https://velog.io/@yoon-bomi/javascript-isNaN-vs-Nember.isNaN</link>
            <guid>https://velog.io/@yoon-bomi/javascript-isNaN-vs-Nember.isNaN</guid>
            <pubDate>Thu, 11 Aug 2022 07:03:21 GMT</pubDate>
            <description><![CDATA[<p><code>isNaN()</code> 을 설명한 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/isNaN">공식문서</a>를 보면 이런 내용이 적혀있다.</p>
<blockquote>
<p>isNaN() 함수는 어떤 값이 NaN인지 판별합니다. isNaN 함수는 몇몇 혼란스러운 케이스을 가지고 있으므로, ECMAScript 2015에서 추가한 <strong>Number.isNaN()으로 바꾸는 편이 좋을 수도 있습니다.</strong></p>
</blockquote>
<p><code>isNaN()</code> 과 <code>Nember.isNaN()</code> 는 어떻게 다를까? 🤔</p>
<br>

<h2 id="isnan-vs-nemberisnan">isNaN() vs Nember.isNaN()</h2>
<p>아래 간단한 예시를 보자.</p>
<pre><code class="language-js">isNaN(&quot;blabla&quot;) // true
Nuber.isNaN(&quot;blabla&quot;) // false

isNaN(NaN) // true
Nuber.isNaN(NaN) // true</code></pre>
<p>분명 같은 string 값을 넣었는데 다른 결과가 도출된다.</p>
<h3 id="why">why?</h3>
<p>isNaN 을 직역하면 <code>이 값이 NaN 이니?</code> 이다.</p>
<p>하지만 <code>isNaN()</code> 은 <strong>매개변수의 값을 강제로 Number 로 캐스팅</strong> 하기 때문에 <code>Numer(&quot;blabla&quot;) = NaN</code> 이 되고, <code>isNaN(NaN) = true</code> 라는 값이 도출 되는 것이다.</p>
<pre><code class="language-js">isNaN(&quot;blabla&quot;) = isNaN(Number(&quot;blabla&quot;)); // 강제로 Number 캐스팅
Number(&quot;blabla&quot;) = NaN

isNaN(&quot;blabla&quot;) // true</code></pre>
<br>

<p><code>&quot;blabla&quot; !== NaN</code> 임에도 불구하고 <code>isNaN(&quot;blabla&quot;) = true</code> 라는 결과가 도출되기 때문에, 사이드 이펙트가 발생할 수도 있어서 온전히 <code>NaN</code> 일 때만 true 를 반환하는 <code>Number.isNaN()</code> 을 사용하라는 의미인 것으로 추측된다. (내 생각)</p>
<pre><code class="language-js">Number.isNaN(&quot;blabla&quot;) // false
Number.isNaN(&quot;1234&quot;) // false
Number.isNaN(&quot;1234ABC&quot;) // fasle
Number.isNaN(1234) // false

Number.isNaN(NaN) // true</code></pre>
<br>

<h2 id="typescript-에서-isnan-을-사용할-때-문제점">typescript 에서 isNaN() 을 사용할 때 문제점</h2>
<p>공식 문서에서도 <code>isNaN()</code> 이 아닌 <code>Number.isNaN()</code> 사용을 권장하고 있다.
자바스크립트는 타입에 대한 정의가 없어서 에러가 나지 않지만 타입스크립트에서 <code>isNaN()</code> 의 매개변수로 number 타입만 받을 수 있어서, string 타입을 넣을 경우 타입 에러가 발생한다.</p>
<p>하지만 <code>Number.isNaN()</code> 는 매개변수를 unknown 타입으로 받기 때문에 string 타입이 들어가도 타입 에러가 발생하지 않는다.</p>
<pre><code class="language-js"> // Argument of type &#39;string&#39; is not assignable to parameter of type &#39;number&#39;. ts(2345)
  if (isNaN(&#39;string&#39;)) { // type error 발생
    console.log(&#39;value is NaN 😕&#39;);
  }


  if (Number.isNaN(&#39;string&#39;)) { // false
    console.log(&#39;value is NaN 😕&#39;);
  }</code></pre>
<br>

<h2 id="결론">결론</h2>
<pre><code class="language-js">isNaN = function(value) {
    Number.isNaN(Number(value));
}</code></pre>
<p>특별한 이유가 없다면 정확한 판단을 위해 <code>isNaN(value)</code> 대신 <code>Number.isNaN(value)</code> 를 사용하자!</p>
<br>
<br>

<p>ref.
공식 문서 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[regex] 날짜 형태 정규식]]></title>
            <link>https://velog.io/@yoon-bomi/regex-%EB%82%A0%EC%A7%9C-%ED%98%95%ED%83%9C-%EC%A0%95%EA%B7%9C%EC%8B%9D</link>
            <guid>https://velog.io/@yoon-bomi/regex-%EB%82%A0%EC%A7%9C-%ED%98%95%ED%83%9C-%EC%A0%95%EA%B7%9C%EC%8B%9D</guid>
            <pubDate>Wed, 03 Aug 2022 07:38:33 GMT</pubDate>
            <description><![CDATA[<ul>
<li>yyyy-mm-dd 형식의 regex</li>
</ul>
<pre><code class="language-java">/^(19|20)\d\d-(0[1-9]|1[012])-([012]\d|3[01])/</code></pre>
<br>

<ul>
<li>yyyy-mm-dd hh:mm:ss 형식의 regex</li>
</ul>
<pre><code class="language-java">/^(19|20)\d\d-(0[1-9]|1[012])-([012]\d|3[01]) (2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9]/</code></pre>
<br>

<ul>
<li>두 형식 모두를 받으려면 ? 와일드 카드(*) 사용</li>
</ul>
<pre><code class="language-java">/^(19|20)\d\d-(0[1-9]|1[012])-([012]\d|3[01]).*/</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ERROR] CredentialsError: Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1]]></title>
            <link>https://velog.io/@yoon-bomi/ERROR-CredentialsError-Missing-credentials-in-config-if-using-AWSCONFIGFILE-set-AWSSDKLOADCONFIG1</link>
            <guid>https://velog.io/@yoon-bomi/ERROR-CredentialsError-Missing-credentials-in-config-if-using-AWSCONFIGFILE-set-AWSSDKLOADCONFIG1</guid>
            <pubDate>Mon, 01 Aug 2022 13:42:57 GMT</pubDate>
            <description><![CDATA[<p>dotenv 설치 후 .env 에 환경변수를 주입해주고 AWS S3 에 파일을 업로드 시도하면 발생했던 에러이다.</p>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/4863a44b-cd45-48af-b1c0-86a0d6817e25/image.png" alt=""></p>
<p>.env 파일에서 <code>AWS_CONFIG_FILE</code> 는 사용하지 않지만 <code>AWS_SDK_LOAD_CONFIG=1</code> 를 적어놨었는데 이 환경변수 때문에 에러가 발생한 것 같다. 해당 환경변수를 제거하니까 정상 동작,, 버킷에도 잘 들어간다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ERROR] InnoDB: Linux Native AIO interface is not supported on this platform. Please check your OS documentation and install appropriate binary of InnoDB.]]></title>
            <link>https://velog.io/@yoon-bomi/ERROR-InnoDB-Linux-Native-AIO-interface-is-not-supported-on-this-platform.-Please-check-your-OS-documentation-and-install-appropriate-binary-of-InnoDB</link>
            <guid>https://velog.io/@yoon-bomi/ERROR-InnoDB-Linux-Native-AIO-interface-is-not-supported-on-this-platform.-Please-check-your-OS-documentation-and-install-appropriate-binary-of-InnoDB</guid>
            <pubDate>Thu, 21 Jul 2022 05:26:21 GMT</pubDate>
            <description><![CDATA[<h2 id="원인">원인</h2>
<ul>
<li>도커 Mysql DB 컨테이너 내부의 <strong>용량 부족</strong>으로 추정(?)</li>
</ul>
<br>

<h2 id="에러-로그">에러 로그</h2>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/88795675-9376-442f-b2a9-288797bbd283/image.png" alt=""></p>
<h2 id="resolve">Resolve</h2>
<pre><code class="language-js">docker system prune --volumes</code></pre>
<ul>
<li>해당 명령어를 이용해 현재 run 중인 도커를 제외한 모든 컨테이너 및 볼륨을 제거 후 재빌드</li>
</ul>
<br>
<br>
<br>
<br>

<p>ref.</p>
<ul>
<li>님 디스크 꽉참 : <a href="https://stackoverflow.com/questions/51252263/mysql-wont-restart-on-ubuntu-server-16-04">https://stackoverflow.com/questions/51252263/mysql-wont-restart-on-ubuntu-server-16-04</a></li>
<li>도커 용량 확보하셈 : <a href="https://stackoverflow.com/questions/66686724/docker-compose-volume-question-and-error-initialize-specified-but-the-data-dir">https://stackoverflow.com/questions/66686724/docker-compose-volume-question-and-error-initialize-specified-but-the-data-dir</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker/ERROR]npm WARN tar ENOSPC: no space left on device]]></title>
            <link>https://velog.io/@yoon-bomi/DockerERRORnpm-WARN-tar-ENOSPC-no-space-left-on-device</link>
            <guid>https://velog.io/@yoon-bomi/DockerERRORnpm-WARN-tar-ENOSPC-no-space-left-on-device</guid>
            <pubDate>Mon, 11 Jul 2022 02:50:37 GMT</pubDate>
            <description><![CDATA[<p>도커에서 <strong>용량 부족</strong>으로 나타나는 에러.</p>
<blockquote>
<p>설정 → Resources 에서 용량을 늘려주어야 한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/4202f6eb-091b-4a73-b794-ed981a0a3347/image.png" alt=""></p>
<p>주기적으로 불필요한 리소스들을 제거 or 줄이고 잘 돌아가더라도 컨테이너들을 새로 빌드해줄 필요가 있다.</p>
<p>(해당 에러 발생 시 Disk image size 를 59 → 64 기가로 늘렸음)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[@ts-expect-error 와 @ts-ignore 은 언제 사용해야할까?]]></title>
            <link>https://velog.io/@yoon-bomi/ts-expect-error-%EC%99%80-ts-ignore-%EC%9D%80-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@yoon-bomi/ts-expect-error-%EC%99%80-ts-ignore-%EC%9D%80-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Mon, 04 Jul 2022 13:45:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yoon-bomi/post/61f16e2f-47e9-4645-826e-31a3f1c3d153/image.jpeg" alt=""></p>
<p>타입스크립트를 쓰다보면 종종 타입스크립트가 타입 추론을 제대로 못하거나, <del>(절대 내가 코딩을 이상하게 해서가 아니라 그냥 타입스크립트가 바보인 경우가 종종 있..)</del> 다른 이유로 인해 타입 에러를 무시해야할 경우가 있다.</p>
<p>필자는 타입스크립트 에러를 무시하기 위해 <code>@ts-ignore</code> 를 더 자주 사용했지만, <code>@ts-expect-error</code> 도 있다는 것을 알게 되어 정리를 해보려 한다.</p>
<br>
<br>

<h2 id="ts-expect-error-와-ts-ignore-의-공통점">@ts-expect-error 와 @ts-ignore 의 공통점</h2>
<p>둘 다 <strong>타입스크립트 에러를 무시하기 위해 사용</strong>한다.</p>
<br>
<br>


<h2 id="ts-expect-error-와-ts-ignore-의-차이점">@ts-expect-error 와 @ts-ignore 의 차이점</h2>
<blockquote>
<p><code>@ts-expect-error</code> :  <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html#-ts-expect-error-comments">typescript 3.9</a> 버전에서 추가되었다.</p>
</blockquote>
<ol>
<li><p><strong>타입스크립트 에러가 발생할 것은 알지만</strong> 어쩔 수 없이 에러를 무시해야 할 때 사용 한다.</p>
<pre><code class="language-ts">delete() {
     // @ts-expect-error NOTE: 타입이 복잡해지지 않도록 ts-expect-error 를 사용한다.
     this.groupIdx = null;
     this.publishEvent(new UserDeletedEvent(this.id, this.groupIdxs));
 }</code></pre>
<p>이 예시에서 groupIdx 의 타입은 <code>number</code> 로 되어있고, 실제 DB 에는 <code>number | null</code> 로 값이 들어갈 수 있다.
하지만 groupIdx 를 <code>number | null</code>  타입으로 지정할 경우, 타입 추론이 너무 복잡해지기 때문에 <code>@ts-expect-error</code> 를 이용해 타입을 단순화했다.</p>
</li>
<li><p>런타임 에러가 발생하면 안된다. <strong>진짜 타입 에러인 경우에만</strong> 사용한다.</p>
</li>
</ol>
<br>

<blockquote>
<p><code>@ts-ignore</code></p>
</blockquote>
<ul>
<li>&#39;아몰랑 일단 넘어가자..&#39; 할 때 사용한다.</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>