<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>개발에 대한 이런저런 고민</title>
        <link>https://velog.io/</link>
        <description>jusqu'au dernier silence</description>
        <lastBuildDate>Fri, 14 Feb 2025 03:04:46 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. 개발에 대한 이런저런 고민. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/p_l_colline" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Logstash 구축 troubleshooting]]></title>
            <link>https://velog.io/@p_l_colline/Logstash-%EA%B5%AC%EC%B6%95-troubleshooting</link>
            <guid>https://velog.io/@p_l_colline/Logstash-%EA%B5%AC%EC%B6%95-troubleshooting</guid>
            <pubDate>Fri, 14 Feb 2025 03:04:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/p_l_colline/post/2651d750-0f89-43cd-9192-eec8ffce02e4/image.png" alt=""></p>
<hr>
<h1 id="trouble-1---db-연결-실패-jdbc관련">Trouble 1 - DB 연결 실패 (JDBC관련)</h1>
<aside>

<p>communications link failure\in\the last packet sent successfully to the server was 0 milliseconds ago. the driver has not received any packets from the server.”</p>
</aside>

<aside>

<p>exception when executing jdbc query {:exception=&gt;sequel::databaseconnectionerror</p>
</aside>

<p>DB에 연결이 안 된다는 오류다. 워낙 추상적이기 때문에 해결책은 여러가지가 있다. 여태까지 봤고, 시도한 걸 정리하자!</p>
<br>

<h2 id="1-mysql의-myconf-파일을-열어-host가-127001로-되어있다면-0000으로-바꿔주는-것">1. Mysql의 my.conf 파일을 열어 host가 127.0.0.1로 되어있다면 0.0.0.0으로 바꿔주는 것</h2>
<ol>
<li>전자는 localhost로의 접속만 허용한다는 뜻이기 때문에 다른 프로그램에서 접근하려면 막힌다. 로컬말고 서버에서 돌릴 때 주로 겪는 문제인듯.</li>
<li>후자는 모든 ip에서의 접속을 허용한다는 뜻. </li>
<li>내 문제는 이게 아니었다</li>
</ol>
<h2 id="2-jdbc-버전-다른-걸로-다운해보기">2. Jdbc 버전 다른 걸로 다운해보기</h2>
<ol>
<li>Mysql이랑 버전 호환 안 되는 거 쓰면 그럴 수도 있다고 봤다</li>
<li>3~4개 정도 시도해봤는데 다 실패</li>
</ol>
<h2 id="3-logstashconf파일-이것저것-수정">3. Logstash.conf파일 이것저것 수정</h2>
<ol>
<li>이 경우는 주로 db url을 잘못 설정했을 때의 해결책으로 자주봤다. Host명이 틀렸거나, 유저 혹은 비번이 틀렸거나, db port가 틀렸거나 혹은 명시를 안 했거나.</li>
<li>문법이 틀렸나해서 스케줄이나 쿼리문, 특히 metadata로 쓰이는 id설정, created_at과 updated_at의 timestamp 형식 수정도 해보았다.<ul>
<li>이 부분은 확실친 않지만 es의 인덱스를 생성할 때 created updated 매핑 설정을 mysql에서 설정해둔 시간 형식과 일치시킨 것도 도움이 되지않았을까 싶다 </li>
</ul>
</li>
<li>나의 주된 문제는 어쨌든 이게 아니라서..</li>
</ol>
<h2 id="4-logstash와-mysql이-같은-네트워크에-있는지-확인">4. logstash와 mysql이 같은 네트워크에 있는지 확인</h2>
<ol>
<li>발생한 이유<ul>
<li>나는 도커를 사용한다. 결론적으로 말하면 두 컨테이너의 네트워크가 달라서 서로를 찾지 못했던 것. 그래서 연결이 안 된 것. 같은 네트워크로 맞춰주니 해결됐다.</li>
</ul>
</li>
<li>설정이 그렇게 된 이유<ul>
<li>원래 플젝A에서 포크떠서 플젝B에서 작업 중이었다. 이때 A에서 작업할 때 올려놨던 컨테이너 중 일부는 중지 및 삭제했고, 일부는 중지조차 안 시키고 계속 가동중이었다. 이 상황에서 B에서 작업을 시작했고 아까 중지했던 컨테이너까지 다시 올려서 작업했다.</li>
<li>컨테이너가 새로 생성되면 네트워크를 지정을 하게 된다. 별 다른 설정이 없으면 디폴트 네트워크로 지정이 되는데, 이게 현재 플젝명_default 이렇게 새로 생성되어 지정된다. 나는 A에서 B로 옮길 때 플젝명도 바꿔서 작업하고 있었다. 그래서 A의 네트워크는 플젝명_default였고 B의 네트워크는 플젝명_personal_default였다.</li>
</ul>
</li>
<li>도커 사용 중이고 나같은 오류가 뜬다면 두 컨테이너의 네트워크가 동일한지 확인해보자!</li>
<li>이걸 왜 늦게 알게 되었냐면, Intelli J에서 오른쪽 탭에서 DB 연결할 땐 문제가 없었기 때문에 MySQL쪽의 문제가 아니라 ES설정의 문제라고 생각하고 있었다. 지금 생각해보니 전혀 상관없는 일이었음을 ㅠㅡㅠ</li>
</ol>
<p><br><br><br></p>
<h1 id="trouble-2---db-인증-문제">Trouble 2 - DB 인증 문제</h1>
<aside>

<p>ctionException: Public Key Retrieval is not allowed&quot;, :cause=&gt;&quot;#&lt;Java::JavaSql::SQLNonTransientConnectionException: Public Key Retrieval is not allowed&gt;&quot;}</p>
</aside>

<p>jdbc_connection_string에 &amp;useSSL=false 추가하면 해결된다.</p>
<p>  <br><br><br></p>
<h1 id="trouble-3---연결은-다-됐는데-es에-업데이트가-안-됨">Trouble 3 - 연결은 다 됐는데 ES에 업데이트가 안 됨</h1>
<h2 id="1-conf파일에-작성한-쿼리를-db에-바로-날려보자">1. conf파일에 작성한 쿼리를 DB에 바로 날려보자</h2>
<ol>
<li>만약 결과값이 없다면 쿼리가 잘못 작성되었거나</li>
<li>DB의 시간대 때문일 것이다</li>
</ol>
<h2 id="2-mysql과-elasticsearch-logstash-시간대-설정">2. MySQL과 ElasticSearch, Logstash 시간대 설정</h2>
<p>당연하지만 시간대가 안 맞으면 NOW()를 날렸을 때 얻는 값이 각 DB마다 다를 것이고 이게 updated_at과 어긋나면 당연히 작동이 안 되다. 나는 서울로 통일했다.</p>
<ol>
<li><p>MySQL</p>
<ul>
<li>SELECT NOW() AS <code>current_time</code>, @@global.time_zone AS global_time_zone, @@session.time_zone AS session_time_zone;</li>
<li>위 쿼리를 날려 현재 DB 시간 설정을 본다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/0ecc6ea2-a93c-4987-b3ac-9ea713678ae4/image.png" alt=""></p>
</li>
</ol>
<pre><code>3. 나는 저 쿼리를 한국기준 오후에 보냈는데 current_time이 저렇게 떴다. 여기서 SYSTEM의 시간 설정을 따른다고 하는데 여기서 SYSTEM은 운영체제를 말하고 운영체제의 시간대를 따르고 있다는 뜻이다. 나는 도커를 사용하기 때문에 시간대가 UTC로 설정이 되어있는 것 같다.
4. 만약 도커를 사용하지 않고 그냥 MySQL을 사용한다면 
SET GLOBAL time_zone = &#39;Asia/Seoul&#39;;
이 쿼리를 날려 해결할 수 있을 것이다.
5. 하지만 나처럼 도커 위의 MySQL이라면 컨테이너 실행시에 환경변수를 추가해주면 된다.
나는 docker-compose파일에
`environment:
-TZ=Asia/Seoul`
이라는 설정을 추가하고 컨테이너와 이미지를 모두 삭제한 뒤 재실행했다.</code></pre><ol start="2">
<li>ElasticSearch<ol>
<li>curl -X GET &quot;localhost:9200/_nodes/settings?pretty” 를 터미널에서 날리거나</li>
<li>GET /_cluster/settings?pretty 를 ES 콘솔에 직접 입력해서 시간을 확인해보자</li>
<li>나의 경우는 persistent settings 와 Transient settings 항목이 모두 비어있었기 때문에 기본값인 SYSTEM 시간대가 사용되고 있었을 것이다.</li>
<li>근데 하여튼 ES에서는 time_zone을 직접 수정하지 못하게 해놨으므로, logstash의 conf파일에서 filter부분에 time_zone을 명시해주면 된다.<br><img src="https://velog.velcdn.com/images/p_l_colline/post/165aef17-0d16-4cd5-90f0-70dca5c33701/image.png" alt=""></li>
</ol>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Logstash를 활용한 MySQL과 ElasticSearch의 동기화 (Docker)]]></title>
            <link>https://velog.io/@p_l_colline/Logstash%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-MySQL%EA%B3%BC-ElasticSearch%EC%9D%98-%EB%8F%99%EA%B8%B0%ED%99%94-Docker</link>
            <guid>https://velog.io/@p_l_colline/Logstash%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-MySQL%EA%B3%BC-ElasticSearch%EC%9D%98-%EB%8F%99%EA%B8%B0%ED%99%94-Docker</guid>
            <pubDate>Thu, 13 Feb 2025 10:43:11 GMT</pubDate>
            <description><![CDATA[<hr>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/94bff875-2327-4626-a2ee-b623a8974c9f/image.png" alt=""></p>
<hr>
<h1 id="mysql과-elasticsearch의-데이터-동기화">MySQL과 ElasticSearch의 데이터 동기화</h1>
<p>두 가지 방법이 있다</p>
<ol>
<li>MySQL에 CRUD가 발생할 때마다 ElasticSearch에 직접 CRUD해주기</li>
<li>Logstash로 MySQL의 변화를 감지해 ElasticSearch에 업데이트하기</li>
</ol>
<p>여러가지 이유로 후자가 합리적이어 보인다.
<br><br><br></p>
<h1 id="구현">구현</h1>
<h2 id="작동-방식">작동 방식</h2>
<ol>
<li>흐름<ol>
<li>Logstash에 JDBC 입력 플러그인 설치 → 이 JDBC 플러그인은 주기적으로 MySQL을 폴링 → 마지막 폴링 이후 MySQL에 발생한 변화를 감지해 ElasticSearch에 업데이트</li>
</ol>
</li>
<li>조건<ol>
<li>MySQL문서가 ElasticSearch로 작성될 때 ES의 _id필드는 MySQL의 id로 설정되어야 함.</li>
<li>MySQL에 레코드가 삽입되거나 업데이트 되면, 그 레코드는 업데이트 또는 삽입 시간을 포함하는 필드를 가져야 함. 이 데이터는 폴링에서 활용됨.</li>
<li>MySQL에서의 데이터 삭제를 ES에 반영하기 위해서는 is_deleted 필드를 가져야 함.
<br><br><h2 id="실제-구현">실제 구현</h2>
</li>
</ol>
</li>
</ol>
<ul>
<li>내 환경 설정과 미리 알면 좋은 것들</li>
</ul>
<p>ES와 키바나는 이미 연결되어있다고 가정</p>
<p>도커 및 도커 컴포즈를 사용한다</p>
<p>JDBC connector는 직접 다운해야하지만 JDBC input plugin은 이미지 다운받으면 자동으로 설정된다</p>
<p>ES와 logstash를 연결하기 위해선 es와 kibana 연결할 때 사용했던 인증서가 필요하다</p>
<p>나는 yml이나 conf같은 설정파일들은 로컬에서 작성하고 도커 컨테이너가 만들어질 때 복사하도록 설정했다 (컨테이너에 직접 들어가서 파일을 만들어도 됨)
<br><br></p>
<h3 id="1-logstash-이미지-다운-및-컨테이너-설정-및-컨테이너-올리기">1) logstash 이미지 다운 및 컨테이너 설정 및 컨테이너 올리기</h3>
<pre><code class="language-yaml">  logstash:
    image: docker.elastic.co/logstash/logstash:8.17.1
    container_name: logstash
    build:
      dockerfile: Dockerfile-logstash
    ports:
      - 5044:5044</code></pre>
<br>
### 2) *.conf 파일 작성

<pre><code class="language-yaml">input {
    jdbc {
        jdbc_driver_library =&gt; &quot;/usr/share/logstash/logstash-core/lib/jars/mysql-connector-java-8.0.30.jar&quot;
        jdbc_driver_class =&gt; &quot;com.mysql.cj.jdbc.Driver&quot;
        jdbc_connection_string =&gt; &quot;jdbc:mysql://mysql:3306/picky?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;autoReconnection=true&quot;
        jdbc_user =&gt; &quot;유저명&quot;
        jdbc_password =&gt; &quot;비밀번호&quot;
        tracking_column =&gt; &quot;unix_ts_in_secs&quot;
        use_column_value =&gt; true
        tracking_column_type =&gt; &quot;numeric&quot;
        schedule =&gt; &quot;*/10 * * * * *&quot;
        statement =&gt; &quot;SELECT *, UNIX_TIMESTAMP(updated_at) AS unix_ts_in_secs FROM movie WHERE (UNIX_TIMESTAMP(updated_at) &gt; :sql_last_value AND updated_at &lt; NOW()) ORDER BY updated_at ASC&quot;
        last_run_metadata_path =&gt; &quot;/usr/share/logstash/.logstash_jdbc_last_run&quot;
    }
}
filter {
    mutate {
        copy =&gt; { &quot;id&quot; =&gt; &quot;[@metadata][movieId]&quot; }
    }

    date {
        match =&gt; [&quot;updated_at&quot;, &quot;yyyy-MM-dd HH:mm:ss.SSSSSS&quot;]
        target =&gt; &quot;updated_at&quot;
        timezone =&gt; &quot;Asia/Seoul&quot;
    }

    mutate {
        remove_field =&gt; [&quot;id&quot;, &quot;@version&quot;, &quot;unix_ts_in_secs&quot;]
    }
}
output {
    stdout { codec =&gt; rubydebug }
    elasticsearch {
        hosts =&gt; [&quot;https://es01:9200&quot;]
        ssl_certificate_authorities =&gt; [&quot;/usr/share/logstash/config/certs/es01/es01.crt&quot;]
        user =&gt; 유저명
        password =&gt; 비밀번호
        index =&gt; &quot;connector-movie&quot;
        document_id =&gt; &quot;%{[@metadata][movieId]}&quot;
    }
}</code></pre>
<ol>
<li>MySQL과 ES를 어떻게 연결할지에 대한 설정파일 및 파이프라인 설정이다.</li>
<li>인덱스 하나당 하나의 설정파일을 만들어야 한다.</li>
<li>파일의 이름은 상관없다. 확장자만 .conf면 된다.</li>
<li>설명<ul>
<li>input<ul>
<li>MySQL에 대한 설정이다. 어떤 JDBC를 쓰고, db url은 무엇이고, 유저명과 비밀번호, 얼마 주기로 동작할지 스케줄러, 어떤 쿼리를 날릴지가 들어간다.</li>
</ul>
</li>
<li>filter<ul>
<li>MySQL에서 가져온 레코드를 ES에 어떻게 넣을지 정제한다. 구현 중에 애를 좀 먹었는데 업데이트할지 안 할지 기준인 timestamp로 쓰이는 필드의 형식이 MySQL과 ES가 다르다고 느꼈다. 그래서 그걸 맞춰주느라 match라는 부분을 넣었다.</li>
<li>timezone은 서울로 맞춰줬다. 서버, mysql, es 모두가 같은 시간대를 공유하도록 해야 오류가 안 난다. 나는 전부 한국으로 통일했다.</li>
</ul>
</li>
<li>output<ul>
<li>정제된 데이터가 ES의 어디에 저장될지 설정한다.</li>
<li>ES와 통신할 때 필요한 인증서의 위치를 설정. 여기까지 읽었다면 많이들 겪었겠지만 ElasticSearch 8 부터는 ssl없이는 연결이 불가능하다. xpack옵션으로 ssl을 disable하면 된다고하는데 그건 버전 7이고, 버전 8부터는 해당 옵션 자체가 적혀있어도 무시된다.</li>
<li>index는 데이터 넣을 인덱스명을 넣으면 된다.
<br><br></li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="3-logstashyml-파일-작성">3) logstash.yml 파일 작성</h3>
<pre><code class="language-yaml">api.http.host: &quot;0.0.0.0&quot;
xpack.monitoring.enabled: true
xpack.monitoring.elasticsearch.hosts: [ &quot;https://es01:9200&quot; ]
xpack.monitoring.elasticsearch.ssl.certificate_authority: &quot;/usr/share/logstash/config/certs/es01/es01.crt&quot;
xpack.monitoring.elasticsearch.username: 유저명
xpack.monitoring.elasticsearch.password: 비밀번호
path.config : &quot;pipeline&quot;</code></pre>
<ol>
<li>위와 같은 설정들이 들어간다.</li>
<li>호스트, 인증서, 유저명, 비밀번호는 직관적으로 보이고,</li>
<li>path.config는 위에서 작성했던 *.conf파일들이 저장되어있는 경로를 명시한다.
<br><br></li>
</ol>
<h3 id="4-dockerfile-logstash-파일-작성">4) Dockerfile-logstash 파일 작성</h3>
<pre><code class="language-yaml">FROM docker.elastic.co/logstash/logstash:8.17.1

USER root

RUN mkdir -p /usr/share/logstash/logstash-core/lib/jars/ &amp;&amp; \
    curl -L -O https://downloads.mysql.com/archives/get/p/3/file/mysql-connector-java-8.0.30.tar.gz &amp;&amp; \
    tar -xzf mysql-connector-java-8.0.30.tar.gz -C /tmp &amp;&amp; \
    ls -lah /tmp &amp;&amp; \
    cp /tmp/mysql-connector-java-8.0.30/mysql-connector-java-8.0.30.jar /usr/share/logstash/logstash-core/lib/jars/ &amp;&amp; \
    chown logstash:logstash /usr/share/logstash/logstash-core/lib/jars/mysql-connector-java-8.0.30.jar &amp;&amp; \
    chmod 755 /usr/share/logstash/logstash-core/lib/jars/mysql-connector-java-8.0.30.jar &amp;&amp; \
    rm -rf /tmp/mysql-connector-java-8.0.30.tar.gz /tmp/mysql-connector-java-8.0.30

RUN mkdir -p /usr/share/logstash/config/certs/ &amp;&amp; \
    chown -R logstash:logstash /usr/share/logstash/config/certs/ &amp;&amp; \
    chmod -R 755 /usr/share/logstash/config/certs/

COPY ./logstash/config /usr/share/logstash/config
COPY ./logstash/pipeline /usr/share/logstash/pipeline
COPY ./certs /usr/share/logstash/config/certs

RUN chown -R logstash:logstash /usr/share/logstash/config/certs/ &amp;&amp; \
    chmod -R 755 /usr/share/logstash/config/certs/

USER logstash</code></pre>
<ol>
<li>dockerfile은 컨테이너를 빌드할 때 필요한 설정파일이다. 간단하게 mysql을 올리는 경우에는 굳이 작성할 필요가 없지만, CI/CD를 위해서 플젝 jars파일을 만든다거나, 지금처럼 logstash 컨테이너를 올리는데 사전에 설정을 해줘야하는 게 있다면 작성해서 넣어주는 것</li>
<li>간단히 설명을 하자면<ul>
<li>첫 번째 RUN에서 JDBC connector를 다운받아서 압축을 풀고 logstash 컨테이너 내부에 적절한 곳 (/usr/share/logstash/logstash-core/lib/jars) 에 넣는다</li>
<li>두 번째 RUN에서는 컨테이너 내부에 인증서를 저장할 폴더를 만들고 권한을 수정한다</li>
<li>세 번째 문단 COPY는 로컬에서 각각 파일을 복사해온다. 순서대로, logstash 파이프라인 설정파일 (logstash.yml), logstash 설정파일(*.cnf), 필요한 인증서 (이 인증서들은 es와 kibana 생성할 때 로컬에 복사해둔 인증서) 복사이다.</li>
<li>네 번째 RUN은 복사된 인증서의 권한을 수정</li>
</ul>
</li>
<li>필요한 파일과 해당 파일들의 위치<ul>
<li>인증서(.crt): 임의의 루트 아무데나 있으면 된다. 나는 설정파일이랑 같이 관리하려고 /usr/share/logstash/config/certs라는 경로를 만들어서 여기 저장했지만, 어디에 저장하든 yml과 conf에 인증서 경로만 잘 적어주면 된다</li>
<li>logstash.yml: logstash 설정파일이다. /usr/share/logstash/config 에 뒀다.</li>
<li>*.cnf: 아무데나 둬도 된다. logstash.yml에서 어느 디렉토리를 참조해야하는지만 명시하면 된다. 근데 usr/share/logstash/pipeline이라는 디렉토리가 기본적으로 생성되기 때문에 나는 여기 저장했다.</li>
</ul>
</li>
</ol>
<hr>
<h1 id="완성">완성!</h1>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/2f66d1d2-0879-482c-9a9e-f025c22864b4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/8fa57cdd-ab76-48e1-8871-fa9060b87171/image.png" alt=""></p>
<p>ES와 MySQL의 자료개수가 똑같다. (movie)
<br></p>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/bd03f776-3e08-4b96-9324-47e82eaaa816/image.png" alt=""></p>
<p>필요했던 데이터들이 잘 들어와있다. 3개의 필드만 보이는데 열면 12개의 필드가 더 있다.</p>
<p><br><br><br></p>
<hr>
<h1 id="회고">회고</h1>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/66c3638a-1be3-480b-8072-e0204cf406bb/image.png" alt=""></p>
<ol>
<li>여러번 포기했다가 다시 도전했다. 대충 12시간 쯤 걸린 것 같다. 만약 도커를 안 쓰고 그냥 설정을 했더라면 훨씬 빨랐을 거다. 근데 나는 팀원들이 docker-compose up -d 하나 하는 것만으로 모든 설정이 끝나길 바랬다. 그래서 꾸역꾸역 해냈다. 
지금은 docker-compose up -d와 인증서를 jks로 변환하는 명령어 두 개만 입력하면 MySQL, Redis, ElasticSearch(인덱스와 플러그인까지), Kibana, Logstash까지 모든 설정이 끝난다.</li>
<li>정말 많고 자잘한 오류를 만났다. 이상하게 자료들도 다 옛날거고 심지어 대부분은 도커를 안 쓰기 때문에 트러블 슈팅하느라 꽤 애먹었다. Elastic에서 공식으로 내놓은 jdbc 연결하는 문서도 2019년에 작성돼서 이걸 믿어도 되나 상당히 의심하며 구현했다.</li>
<li>예전 프로젝트할 때 이미 사용했었는데 사실 ElasticSearch는 돈주고 api 결제하면 인증서부터해서 갖가지 다른 설정이 아예 필요가 없어진다 (…)</li>
</ol>
<br>

<h1 id="개선점">개선점</h1>
<ol>
<li>개선점은 아니고 추후에 해보면 좋을 것 같은 사항인데, logstash는 단순히 RDBMS와 ES의 자료 동기화를 위해서만 쓰이는 게 아니다. 사실 이건 기능 중 일부이다. 다양한 플랫폼과 자료형을 지원하기 때문에 로그나 자료수집해서 분석하는 도구로도 많이 사용하는 것 같았다. 나중에 서비스 모니터링 환경을 다시 구축하게 된다면 접목해보는 것도 좋을 것 같다.
<br><br><br></li>
</ol>
<hr>
<h1 id="reference">Reference</h1>
<p><a href="https://www.elastic.co/kr/blog/how-to-keep-elasticsearch-synchronized-with-a-relational-database-using-logstash">https://www.elastic.co/kr/blog/how-to-keep-elasticsearch-synchronized-with-a-relational-database-using-logstash</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Elastic Stack - Logstash ]]></title>
            <link>https://velog.io/@p_l_colline/Elastic-Stack-Logstash</link>
            <guid>https://velog.io/@p_l_colline/Elastic-Stack-Logstash</guid>
            <pubDate>Wed, 12 Feb 2025 13:22:04 GMT</pubDate>
            <description><![CDATA[<ul>
<li>ELK?<ul>
<li>ElasticSearch, Logstash, Kibana의 앞글자를 따 통칭하는 단어</li>
<li>ElasticSearch: 검색 및 분석 엔진</li>
<li>Logstash: 데이터를 수집하고 변환 및 데이터 처리 파이프라인</li>
<li>Kibana: 데이터 시각화</li>
</ul>
</li>
</ul>
<h1 id="logstash란">Logstash란?</h1>
<ol>
<li>하는 일<ol>
<li>‘인풋’ 데이터를 ‘필터링’해 ‘아웃풋’으로 내보냄</li>
<li>데이터 처리 파이프라인</li>
<li>데이터 실시간 파싱 및 변환</li>
</ol>
</li>
<li>인풋<ol>
<li>다양한 데이터 포맷 지원</li>
<li>로그, 매트릭스, 웹 애플리케이션, 데이터 소스, AWS 서비스 등에서 인풋 데이터 수집 가능</li>
</ol>
</li>
<li>데이터 필터링<ol>
<li>데이터를 소스에서 저장소로 옮길 때 분석에 유용한 구조로 변환</li>
<li>주요 기능<ol>
<li>구조화되지 않은 데이터를 구조화 (grok)</li>
<li>IP주소에서 지리적 좌표를 추출</li>
<li>개인정보 식별 데이터 익명화</li>
</ol>
</li>
</ol>
</li>
<li>아웃풋<ol>
<li>데이터를 원하는 곳으로 전송할 수 있는 다양한 출력 옵션 제공</li>
<li>ElasticSearch는 검색과 분석을 도와주는 기본 출력 옵션이지만 선택에 따라 변경할 수 있음</li>
</ol>
</li>
<li>관리<ol>
<li>단일 UI로 중앙에서 관리 가능 (kibana)</li>
<li>파이프라인 매니지먼트 UI를 통해 logstash 배포를 쉽게 조정하고 관리 가능</li>
<li>여러 DB를 한 곳에서 볼 수 있음</li>
</ol>
</li>
<li>아웃풋의 활용<ol>
<li>유입 분석, 트래픽, 성과 추이 등의 데이터를 실시간으로 확인</li>
</ol>
</li>
</ol>
<h1 id="reference">Reference</h1>
<p><a href="https://www.elastic.co/logstash">https://www.elastic.co/logstash</a></p>
<p><a href="https://blog.bizspring.co.kr/%ED%85%8C%ED%81%AC/logstash-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%88%98%EC%A7%91-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8/">https://blog.bizspring.co.kr/%ED%85%8C%ED%81%AC/logstash-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%88%98%EC%A7%91-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8/</a></p>
<p><a href="https://blog-sluv.tistory.com/m/entry/Elastic-Search-Logstash-mysql-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0">https://blog-sluv.tistory.com/m/entry/Elastic-Search-Logstash-mysql-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS-Slack 실시간 모니터링 환경 구축 (Docker)]]></title>
            <link>https://velog.io/@p_l_colline/AWS-Slack-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95-Docker</link>
            <guid>https://velog.io/@p_l_colline/AWS-Slack-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95-Docker</guid>
            <pubDate>Mon, 10 Feb 2025 11:10:17 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/p_l_colline/post/74b15206-22c3-4e60-9846-f0ab78c4a8a3/image.png" alt=""></p>
<hr>
<h1 id="개요">개요</h1>
<p>현직자에게 프로젝트 멘토링을 받고 얻은 피드백은, 연결되어있는 외부 api의 서버가 닫혀있거나 하는 등의 문제에 대해 어떻게 대응하고 있냐는 질문이었다.</p>
<p>이에 대해 대책을 내고자 모니터링 환경을 구축했다.</p>
<h2 id="프로젝트-기본-세팅">프로젝트 기본 세팅</h2>
<p>aws서버에 도커 컨테이너가 올라가있다.</p>
<hr>
<h1 id="구현">구현</h1>
<h2 id="i-ec2-도커-로그-수집-환경-세팅">I. EC2-도커 로그 수집 환경 세팅</h2>
<h3 id="1-cloudwatch---로그-수집-설정">1) CloudWatch - 로그 수집 설정</h3>
<ol>
<li>로그 → 로그 그룹 → 로그 그룹 생성</li>
<li>생성한 로그 그룹 눌러서 → 로그 스트림 → 로그 스트림 생성</li>
</ol>
<h3 id="2-iam---iam-역할-및-권한-추가">2) IAM - IAM 역할 및 권한 추가</h3>
<ol>
<li>역할 생성</li>
<li>AWS서비스 → 사용 사례: EC2</li>
<li>CloudWatchFullAccess 권한 추가 및 정책 선택 → 역할 이름 지정, 생성</li>
</ol>
<h3 id="3-ec2---인스턴스-보안-설정-추가">3) EC2 - 인스턴스 보안 설정 추가</h3>
<ol>
<li>로그를 수집하고자하는 인스턴스 선택 후 상단의 작업버튼 → 보안 → IAM 역할 수정</li>
<li>2번에서 생성한 IAM 역할 추가해서 업데이트</li>
</ol>
<h3 id="4-ec2---도커-컨테이너-run-옵션-추가">4) EC2 - 도커 컨테이너 run 옵션 추가</h3>
<pre><code class="language-java">sudo docker run \
            --add-host ${{ secrets.ADDITIONAL_HOST }} \
            --log-driver=awslogs \
            --log-opt awslogs-group=[로그 그룹 이름] \
            --log-opt awslogs-stream=[로그 스트림 이름] \
            --log-opt awslogs-region=[지역명] \
            -d -p 80:8080 --name [이미지명] \
            -e SPRING_PROFILES_ACTIVE=[spring플젝 실행 옵션 설정] \
            ${{ secrets.DOCKER_USERNAME }}/[이미지명]</code></pre>
<ol>
<li>위 명령어에서 봐야하는 부분은 log가 붙은 부분들이다. 나머지 —add-host나 SPRING_PROFILE 등등은 내 프로젝트에서 필요한 다른 설정들 때문에 추가된 부분이다.</li>
</ol>
<h3 id="5-cloudwatch---로그-제대로-수집되나-확인">5) CloudWatch - 로그 제대로 수집되나 확인</h3>
<ol>
<li>로그 그룹 → 로그 스트림</li>
<li>도커 로그 그대로 들어오는지 확인</li>
</ol>
<br>
<br>

<h2 id="ii-수집할-로그-필터링">II. 수집할 로그 필터링</h2>
<h3 id="1-cloudwatch---수집하고자-하는-데이터의-기준-설정">1) CloudWatch - 수집하고자 하는 데이터의 기준 설정</h3>
<ol>
<li>로그 그룹 → 지표 필터 → 지표 필터 생성</li>
<li>필터 패턴 정의 → 이벤트로 지정할 문자열 지정. 나는 우선 ERROR를 입력해두어 로그에 ERROR라는 문자열이 포함되는 로그를 수집하게 했다.</li>
<li>패턴 테스트 → 로그 스트림 지정 후 패턴테스트 진행 → 결과 똑바로 뜨면 성공</li>
<li>필터 이름, 지표 네임스페이스, 지표 이름, 지표값 등 설정</li>
<li>검토 및 생성</li>
</ol>
<br>
<br>

<h2 id="iii-경보와-슬랙-연동">III. 경보와 슬랙 연동</h2>
<h3 id="1-simple-notification-service---마이크로-서비스-및-서버리스-애플리케이션에-대한-게시구독-메시징">1) Simple Notification Service - 마이크로 서비스 및 서버리스 애플리케이션에 대한 게시/구독 메시징</h3>
<ol>
<li>주제 → 주제 생성 (혹은 주제 이름에 이름 작성하고 다음 단계 버튼)</li>
<li>유형은 표준, 나머지는 본인이 편한대로 설정</li>
</ol>
<h3 id="3-aws-chatbot---slack과-연결">3) AWS chatbot - slack과 연결</h3>
<ol>
<li>클라이언트 구성 → 클라이언트 유형: slack</li>
<li>연결할 슬랙 채널에 액세스 허용</li>
<li>Slack 채널: 연결할 채널 ID 입력</li>
<li>권한: 채널 역할 선택</li>
<li>채널 역할: 템플릿을 사용하여 IAM 역할 생성 → 역할 이름 지정<ol>
<li>템플릿에 알림권한, Resource Explorer 권한 추가 되어있으면 성공</li>
</ol>
</li>
<li>알림: SNS 주제: 아까 생성한 SNS 주제 선택 후 저장(생성)</li>
</ol>
<h3 id="4-slack---채널에-초대">4) Slack - 채널에 초대</h3>
<ol>
<li>연결한 슬랙 채널에 들어가 채팅창에 /invite @aws 를 입력해 aws를 초대</li>
</ol>
<h3 id="5-aws-chatbot---슬랙에-테스트-메시지-전송">5) AWS chatbot - 슬랙에 테스트 메시지 전송</h3>
<ol>
<li>생성된 클라이언트에서 세부정보 → 테스트 메시지 전송 → 연동한 슬랙 채널에 메시지 올라오면 연동 완료</li>
</ol>
<h3 id="6-cloudwatch---경보-기준-설정">6) CloudWatch - 경보 기준 설정</h3>
<p>A분 동안 로그를 수집했을 때, 위에서 정한 지표 필터에 B개 이상의 이벤트가 쌓이면 경보를 발생하겠다고 가정.</p>
<ol>
<li>경보 → 모든 경보 → 경보 생성</li>
<li>지표 및 조건 지정
 a. 지표 선택 → 위에서 생성한 지표 선택
 b. 기간: A분 동안의 로그를 수집해서 경보를 낼 건지 지정
 c. 조건: 정적, 보다 크거나 같음
 d. …보다: B(숫자)</li>
<li>작업 구성
 a. 경보 상태 트리거: 경보상태, 기존 SNS 주제 선택, 아까 생성한 SNS 선택
 b. 경보 상태 트리거:  데이터 부족, 기존 SNS 주제 선택, 아까 생성한 SNS 선택<pre><code> i. (이걸 추가해주는 이유는 경보 생성 초기에는 쌓인 데이터가 부족하기 때문에, 실제로 이 경보가 제대로 작동하고 있는지 확인하기가 애매하기 때문이다. 세팅이 완료되고 나면 지워도 된다)</code></pre></li>
<li>이름 및 설명 추가
 a. 경보 이름 및 경보 설명 추가<pre><code> i. 슬랙에서 보게될 이름 및 설명이다.</code></pre></li>
<li>경보 생성</li>
</ol>
<hr>
<h1 id="결론">결론</h1>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/09b1ae93-73bb-4be8-b61c-b42d8085b41a/image.png" alt=""></p>
<p>이제 ERROR라는 문구가 포함된 로그는 실시간으로 슬랙으로 받아볼 수 있다.</p>
<p>이제 로그를 수집해서 필터링해서 슬랙으로 전송하는 것까지 완성했다. 다음 할 일은 연동된 외부 api가 다운될 경우의 로그를 어떻게 수집할지 결정하고, 다운된 외부 api를 대체할만한 방안을 구현하는 것이다.</p>
<hr>
<h1 id="reference">Reference</h1>
<p><a href="https://softmoca.tistory.com/333">https://softmoca.tistory.com/333</a></p>
<p><a href="https://ssyoni.tistory.com/24">https://ssyoni.tistory.com/24</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[맥북의 Airplay는 포트 5000번을 사용한다]]></title>
            <link>https://velog.io/@p_l_colline/%EB%A7%A5%EB%B6%81%EC%9D%98-Airplay%EB%8A%94-%ED%8F%AC%ED%8A%B8-5000%EB%B2%88%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C%EB%8B%A4</link>
            <guid>https://velog.io/@p_l_colline/%EB%A7%A5%EB%B6%81%EC%9D%98-Airplay%EB%8A%94-%ED%8F%AC%ED%8A%B8-5000%EB%B2%88%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C%EB%8B%A4</guid>
            <pubDate>Tue, 04 Feb 2025 17:59:36 GMT</pubDate>
            <description><![CDATA[<p>LLM 데이터 라벨링 업무를 하는데, 파이썬에서 flask를 이용해서 서버를 올려야하는게 있었다. 일단 서버는 올렸는데 프론트 서버에서 요청을 보내니 자꾸 CORS 에러가 떴다. CORS 관련된 것들 아무리 고쳐봐도 해결이 안 되길래 구글링을 좀 해보니,</p>
<p>맥북 airplay 기능이 5000번 포트를 사용한다고 한다.
파이썬 flask 서버의 기본 포트 또한 5000번 이다.</p>
<p>airplay 끄니 해결됐다.</p>
<p>가끔 이런 어이없는 에러를 마주하면 헛웃음이 난다.</p>
<hr>
<h2 id="의문1">의문1</h2>
<p>글쓰던 중 갑자기 의문점이 들었다.</p>
<p><strong>_포트 충돌이라면 애초에 파이썬 서버가 올라가면 안 되지 않나? _</strong></p>
<p>답은 디버그 모드와 프로덕션 모드의 프로세스 실행 방식의 차이 때문에 가능하다는 것이다.
(라고 생각했지만 아래의 의문2를 풀어나가다가 이게 아닐 수도 있겠다는 생각을 하게 되었다.)</p>
<p>내 코드는 app.run(debug=True) 이렇게 디버그 모드로 실행이 되고있었다. 이때는 서버가 올라갔다.
근데 app.run()으로 프로덕션 모드로 실행하니 포트 충돌 에러가 뜨며 서버 자체가 올라가지 않았다.</p>
<h3 id="디버그-모드-vs-프로덕션-모드">디버그 모드 VS 프로덕션 모드</h3>
<p>디버그 모드와 프로덕션 모드의 차이는 프로세스 실행 방식이다. 디버그 모드는 두 개의 프로세스를 실행시켜 코드에 수정사항이 생기면 바로 반영할 수 있게한다. 반면 프로덕션 모드는 하나의 프로세스만 실행된다.
(디버그 모드에서 코드 변경이 감지될 경우 새 프로세스를 생성하고 이것으로 flask를 실행한다.)</p>
<h3 id="디버그-모드-실행-과정">디버그 모드 실행 과정</h3>
<p>디버그 모드가 실행될 때 첫 번째 프로세스는 생성되고 바로 죽는데 이때 5000포트를 잠깐 잡고있다가 죽는다. 이후 flask의 SO_REUSEADDR를 사용해서, 사용이 끝난 포트를 즉시 다시 사용할 수 있도록 허용한다. (일반적으로 OS는 소켓이 닫힌 이후에도 일정 시간동안 포트를 사용하지 못하게 대기해둔다.) 그래서 첫 번째 프로세스가 종료되면서 -&gt; 5000번 포트가 사용 가능 상태가 되고 -&gt; 두 번째 프로세스가 그걸 잡아서 실행한 것이다. 
(첫 번째 프로세스가 애초에 어떻게 이미 점유된 5000번 포트를 잡았나? 이건 찾아봤는데 아직 못찾았다 ㅠㅠ)</p>
<h3 id="프로덕션-모드-실행-과정">프로덕션 모드 실행 과정</h3>
<p>반면 프로덕션 모드에서는 SO_REUSEADDR를 사용하지 않고 하나의 프로세스만 실행되기 때문에 5000번이 이미 점유되어 있다면 충돌이 발생한다.</p>
<hr>
<h2 id="의문2">의문2</h2>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/b534d50a-59de-4f23-afc3-db42095a782a/image.png" alt="">
의문1을 풀어내던 중 코드를 프로덕션 모드로 실행해봤는데 실행이 됐다. airplay 기능이 켜져있는데도 실행이 됐다. 우연인가 싶어 몇십분동안 간간히 실행해봤는데 계속 실행이 됐다.</p>
<p>아까는 정말로 디버그 모드만 실행이 됐었는데 지금은 프로덕션 모드까지 실행이 되니 당황스럽다. 의문1에 대한 답 조차도 무의미해졌다. </p>
<hr>
<h2 id="정리">정리</h2>
<p>해결방법의 정리가 아니라 이 일이 발생한 과정의 시간순 정리이다.</p>
<ol>
<li>디버그 모드로 실행했을 때 서버가 올라가긴 했지만 계속해서 CORS 에러가 났다.</li>
<li>프로덕션 모드로 실행을 하니 5000번 포트가 이미 점유되었다며 실행조차 되지 않았다.</li>
<li>5000번 포트는 맥북의 airplay 기능이 점유하고 있었다. 이 기능을 비활성화하니 문제가 해결됐다. 디버그 모드에서도 CORS에러가 안 뜨고, 프로덕션 모드도 문제없이 실행되었다.</li>
<li>업무 마무리 후 airplay를 다시 활성화했다.</li>
<li>위의 의문1, 의문2를 정리하던 중 아까 쓰던 코드를 다시 실행해봤는데 포트 충돌 없이 실행이 된다. 디버그 모드와 프로덕션 모드에서 둘 다 된다. </li>
</ol>
<p>왜 아까는 안 되고 지금은 될까
진짜 왜지</p>
<p>검색해도 자료가 너무 적다</p>
<p>미래의 발전한 내가 와서 이 글을 수정할 날이 오길..</p>
<hr>
<h2 id="reference">Reference</h2>
<p><a href="https://github.com/viniciuschiele/flask-apscheduler/issues/139">https://github.com/viniciuschiele/flask-apscheduler/issues/139</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MySQL n-gram VS ElasticSearch 성능 비교, ElasticSearch 도입기]]></title>
            <link>https://velog.io/@p_l_colline/MySQL-n-gram-VS-ElasticSearch-%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90-ElasticSearch-%EB%8F%84%EC%9E%85%EA%B8%B0</link>
            <guid>https://velog.io/@p_l_colline/MySQL-n-gram-VS-ElasticSearch-%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90-ElasticSearch-%EB%8F%84%EC%9E%85%EA%B8%B0</guid>
            <pubDate>Thu, 30 Jan 2025 06:09:20 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="개요">개요</h1>
<p>영화 리뷰 및 소셜 플랫폼 서비스 개발 당시 영화 검색 기능을 구현했다. 검색에 자동완성 기능까지 붙이려했었다. 개발할 때부터 MySQL로는 무리가 아닐까 생각헀지만 n-gram이라는 기능을 한 번 써보고 싶었기 때문에 우선 MySQL로 검색기능을 구현했다. 하지만 역시나 만족할만한 성능이 나오지 않았고, ElasticSearch를 도입하였다.</p>
<p>결론을 말하자면 속도가 약 4배 상승했으며 오타보정 및 자동완성 기능까지 붙일 수 있었기에 만족했다.</p>
<hr>
<h1 id="기술-소개">기술 소개</h1>
<h2 id="mysql-n-gram">MySQL n-gram</h2>
<p>MySQL에는 full-text parser가 내장되어 있다. 이는 fulltext 인덱스를 사용해 텍스트를 검색할 때 입력된 텍스트를 토큰으로 나누는 기능이다. 이는 띄어쓰기를 기준으로 단어의 시작과 끝을 파악하기 때문에 영어같은 언어에는 적합하다. 하지만 한국어의 경우에는 띄어쓰기만으로 텍스트를 토큰화하기 어려우므로 이는 우리가 원하는 검색 기능을 구현하기에 부적합하다.</p>
<p>한국어, 중국어, 일본어 같은 언어를 위해 MySQL에는 n-gram이라는 기능 또한 내장되어 있다. 이는 미리 설정해둔 토큰화 단위에 따라 단어를 토큰화 한다.</p>
<aside>

<p>예를 들어 ‘abcd’라는 단어의 경우</p>
<p>n=1: &#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;
n=2: &#39;ab&#39;, &#39;bc&#39;, &#39;cd&#39;
n=3: &#39;abc&#39;, &#39;bcd&#39;
n=4: &#39;abcd&#39;</p>
<p>로 분석된다.</p>
</aside>

<p>만약 한 글자라도 일치하면 검색결과를 내고싶다면 토큰화 단위를 1로 설정하면 된다.</p>
<aside>

<p>설정 방법은 아래를 콘솔에서 명령하면 된다.</p>
<p>mysqld --ngram_token_size=1</p>
</aside>

<h2 id="elasticsearch-이하-es">ElasticSearch (이하 ES)</h2>
<p>분산형 검색 및 분석 엔진</p>
<p>데이터베이스처럼 사용할 수 있지만 검색과 분석에 특화된 도구</p>
<p>이렇게 속도가 빠를 수 있는 이유는 역색인(inverted index) 구조를 사용하기 때문이다. 이는 단어 중심으로 인덱스를 만드는 방법을 말한다. (MySQL은 행 구조)</p>
<p>MySQL에서 특정 단어를 검색을 하면 모든 행을 읽어가며 해당 단어가 포함되어있는지 검사한다. 하지만 역색인 방식은 단어별로 인덱스를 생성하기 때문에 속도가 높다.</p>
<h3 id="역색인">역색인</h3>
<p>나는 처음에 이 개념이 이해가 좀 안 됐다.</p>
<p>쉽게 말하자면, 각 단어를 key로, 그 단어가 포함된 문서 ID값을 value로 저장하는 방식이다.</p>
<p>예를 들어</p>
<aside>

<ol>
<li>나는 강아지를 좋아해</li>
<li>나는 고양이를 좋아해</li>
<li>강아지와 고양이는 귀엽다</aside>

</li>
</ol>
<p>라는 문장이 있다면 ES에서 역색인을 생성하는 방식은</p>
<aside>

<p>강아지 [1, 3]</p>
<p>고양이 [2, 3]</p>
<p>좋아해 [1, 2]</p>
<p>귀엽다 [3]</p>
</aside>

<p>과 같이 각 단어가 어느 문장에서 등장하는 지 저장한다. 이미 토큰별로 분류해서 인덱스가 만들어져 있기 때문에 문서를 하나하나 읽지 않아도 되는 것이다.</p>
<h3 id="한국어에서-역색인의-사용">한국어에서 역색인의 사용</h3>
<p>사실 위 문장을 띄어쓰기대로 나누면</p>
<aside>

<p>나는, 강아지를, 좋아해</p>
</aside>

<p>단어 단위로 저장되지 않는다. 그렇기에 형태소 분석기를 사용해야하는데 대표적인 한국어 형태소 분석기로는 nori가 있고 우리또한 그것을 사용했다.</p>
<p>이를 사용하면</p>
<aside>

<p>나,는,강아지,를,좋아,해</p>
</aside>

<p>와 같이 형태소마다 분리되기 때문에 한국어 검색 결과의 정확도를 더욱 높일 수 있다.</p>
<hr>
<h1 id="구현">구현</h1>
<h2 id="mysql">MySQL</h2>
<p>테이블에</p>
<pre><code class="language-json">ALTER TABLE movie
ADD FULLTEXT (title) WITH PARSER ngram;</code></pre>
<p>속성을 추가해준다.</p>
<pre><code class="language-json">SELECT * FROM movie 
WHERE MATCH(movie) AGAINST(&#39;바람&#39; IN BOOLEAN MODE);</code></pre>
<p>이런식으로 쿼리문을 작성해주면 ‘바람’이라는 단어가 포함된 제목을 가진 영화가 검색되게 할 수 있다.</p>
<h2 id="elasticsearch">ElasticSearch</h2>
<p>이건 환경세팅부터 document생성 및 nori 플러그인 추가 등 한 일이 너무 많으므로 나중에 다른 게시글로 작성하여 붙여두겠다.</p>
<hr>
<h1 id="성능-비교">성능 비교</h1>
<p>테스트 도구: Jmeter
<img src="https://velog.velcdn.com/images/p_l_colline/post/c78b2c28-eda3-4eac-9728-3243578705f7/image.png" alt=""></p>
<p>요청이 몰렸을 때 처리 성능보다는 평균적 검색 성능을 보고싶었기 때문에 100번 반복으로 설정했다</p>
<h2 id="데이터-7800개일-때">데이터 7800개일 때</h2>
<h3 id="mysql-1">MySQL</h3>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/de4d3b64-4042-4f75-961e-cd8514712936/image.png" alt=""></p>
<p>평균 63ms</p>
<p>반복할 수록 소요시간이 짧아지는 경향을 보였다</p>
<h3 id="elasticsearch-1">ElasticSearch</h3>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/021cc514-e434-49d9-b6bf-febf0efac833/image.png" alt=""></p>
<p>평균 15ms</p>
<p>마찬가지로 반복할 수록 소요시간이 짧아지는 경향을 보였다</p>
<h2 id="데이터-15000개일-때">데이터 15000개일 때</h2>
<h3 id="mysql-2">MySQL</h3>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/dbfb0b10-1dc9-45a5-8dc4-dae42916891d/image.png" alt=""></p>
<p>평균 65ms</p>
<h3 id="elasticsearch-2">ElasticSearch</h3>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/79b6db5b-13d5-408c-84e8-4f32fa914511/image.png" alt=""></p>
<p>평균 15ms</p>
<h2 id="데이터-양에-따른-성능-비교">데이터 양에 따른 성능 비교</h2>
<p>데이터는 약 2배 늘어났는데 MySQL에서 2ms정도 느려진 걸 볼 수 있었다. 더 많은 데이터로 비교해본다면 더 명확한 성능을 볼 수 있을 것 같다. </p>
<hr>
<h1 id="결론">결론</h1>
<h2 id="속도">속도</h2>
<p>ElasticSearch가 약 4배 빨랐다.</p>
<p>ElasticSearch와 MySQL 성능을 비교해놓은 글들을 보면 약 20배의 속도 차이가 났다고 하는데 아마 이건 mysql의 n-gram이 아닌 like와 같은 쿼리를 날렸을 때의 결과 아닐까 추측해본다.</p>
<h2 id="기능">기능</h2>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/57d9af8e-3514-4dd2-aba0-e18555f0e87f/image.png" alt="">
<img src="https://velog.velcdn.com/images/p_l_colline/post/a63dbe20-17dc-44c3-ad31-cd40633cbb00/image.png" alt=""></p>
<p>mysql은 오타가 있을 경우 검색 결과가 나오지 않는다.</p>
<p>ES 형태소 분석때문에 오타가 있더라도 비슷한 결과들을 보여준다. 오타 민감도는 설정에서 마음대로 조절할 수 있기 때문에 조금만 비슷해도 보여줄 것인지, 한 글자만 살짝 다를 때만 보여줄 것인지 등을 설정할 수 있다.</p>
<p>자동완성과 오타보정을 생각해본다면, ES를 사용한 검색결과가 사용자 경험에 긍정적 영향을 끼칠 것이라 생각한다.</p>
<h2 id="후기1---es적용-까다로움">후기1 - ES적용 까다로움</h2>
<p>사실 ES 적용하기가 상당히 까다로웠다. 처음 접하는 기술이기 때문에 처음부터 끝까지 공부해야했고, ES 버전8 이후에는 SSL 사용이 필수이기 때문에 보안관련 문제때문에 세팅하는데 시간이 더 오래 걸렸다.</p>
<p>혼자 쓰는 세팅이라면 내가 편한대로 했겠지만, 팀원들의 환경 세팅 통일을 위해서는 프로세스를 최대한 간편화 해야했다. 지금은 docker-compose up -d 와 명령어 두 개 던지면 로컬에서는 설정이 완료되게 해두었다.</p>
<p>며칠을 고생해가며 설정해서 결국은 보이는 것처럼 성공했다. 지금은 로컬과 서버에서 문제없이 돌아간다.</p>
<h2 id="후기2---웹-애플리케이션-및-docker-전반의-공부">후기2 - 웹 애플리케이션 및 docker 전반의 공부</h2>
<p>보안 문제 때문에 웹 애플리케이션 전반의 흐름에 대해 많이 공부해야했고 특히나 보안은 이번에 다시 한 번 정리할 수 있어서 좋았다.</p>
<p>팀원과의 환경 통일 및 배포시에 충돌 최소화를 위해 docker도 사용하고 있었는데 ES를 도입하면서 컨테이너의 개념을 다시 정리하고 이해할 수 있는 기회였다.</p>
<h2 id="총평">총평</h2>
<p>고생하긴 했지만 성능과 기능적 측면에서 ElasticSearch가 mysql의 n-gram보다 월등함을 보았기에 꽤 만족스럽다.</p>
<hr>
<h1 id="번외-더미데이터-생성">번외) 더미데이터 생성</h1>
<p>성능 비교를 위해서 MySQL과 ES 모두에 데이터를 넣어줘야했다. 우리 DB의 영화 테이블에는 상당히 많은 컬럼이 있고, 또 다른 테이블과의 연관관계도 많기 때문에 일반적인 더미데이터 생성 방식으로는 원하는 양질의 데이터는 얻기 힘들다고 판단했다.</p>
<p>그런데 우리 프로젝트에는 이미 TMDB에서 데이터를 가져와 자동으로 영화를 추가하는 코드가 있다.</p>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/b93f3784-bdb7-4f6f-b809-eb233f9501a5/image.png" alt=""></p>
<p>하지만 안타깝게 TMDB에서는 본인들이 가지고있는 영화id의 리스트를 제공하지는 않는다. 그리하여 조금 무식한 방법이긴하지만 이렇게 반복문 걸어놓고 돌렸다. </p>
<p>우리는 MySQL과 ES의 데이터를 주기적으로 동기화 해주고 있었기 때문에 양쪽 모두에 동일한 데이터를 넣을 수 있었고 성능 테스트 환경을 구성할 수 있었다.</p>
<p>물론 이는 성능 테스트 뿐만이 아닌, 서비스 운영에 필요한 데이터를 넣는 작업이라고도 볼 수 있겠다.</p>
<hr>
<h1 id="reference">Reference</h1>
<h3 id="mysql-3">MySQL</h3>
<p><a href="https://dev.mysql.com/doc/refman/8.4/en/fulltext-search-ngram.html">https://dev.mysql.com/doc/refman/8.4/en/fulltext-search-ngram.html</a></p>
<h3 id="elasticsearch-3">ElasticSearch</h3>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/elasticsearch-intro-what-is-es.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/elasticsearch-intro-what-is-es.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[IoC, DI]]></title>
            <link>https://velog.io/@p_l_colline/IoC-DI</link>
            <guid>https://velog.io/@p_l_colline/IoC-DI</guid>
            <pubDate>Tue, 24 Sep 2024 14:52:41 GMT</pubDate>
            <description><![CDATA[<p>스프링의 기본 컨셉에 대한 정리</p>
<h1 id="ioc---inversion-of-control">IoC - Inversion of Control</h1>
<ol>
<li>정의<ol>
<li>제어의 역전</li>
</ol>
</li>
<li>예시<ol>
<li>controller나 service 같은 객체들의 동작을 구현은하지만, 해당 객체들이 어느 시점에 호출될지는 신경 안 쓴다. 프레임워크가 해당 객체를 생성하고 메서드 호출하고 소멸시킨다</li>
<li>interface에 메서드를 정의하고 해당 interface를 상속받은 class에서 해당 메서드의 실제 구현을한다. 이러면 구현은 하위 클래스에서 하긴하지만 해당 메서드의 호출을 상위에 있는 interface에서 하므로 제어의 역전이 일어남 (템플릿 메서드 패턴에서도 동일)</li>
</ol>
</li>
<li>장점<ol>
<li>프로그램의 진행 흐름과 구체적인 구현을 분리할 수 있음</li>
<li>개발자는 비즈니스 로직에 집중 가능</li>
<li>구현체 사이의 변경이 용이</li>
<li>객체 간의 의존성이 낮아짐</li>
</ol>
</li>
<li>참고<ol>
<li>라이브러리와 프레임워크의 차이는 IoC로 설명 가능하다. 라이브러리는 어플리케이션 제어의 흐름을 가져가지 않는다</li>
</ol>
</li>
</ol>
<h1 id="di---dependency-injection">DI - Dependency Injection</h1>
<ol>
<li>정의<ol>
<li>애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것</li>
<li>객체 인스턴스를 생성하고 그 참조값을 전달해서 연결</li>
<li>의존대상을 직접 생성 혹은 결정하는 것이 아니라 외부로부터 주입받는 것</li>
<li>참고: 의존성 - B에서 변경이 일어났는데 A에도 영향을 미치는 것</li>
</ol>
</li>
<li>주입 방법<ol>
<li>생성자주입, 수정자 주입, 필드 주입, 일반 메서드 주입</li>
</ol>
</li>
<li>구현 방법<ol>
<li>설정정보 - config파일 직접작성 후 객체 직접 생성해서 주입</li>
<li>설정정보 - config파일 직접작성 후 @Configuration 달아서 주입</li>
<li>컴포넌트 스캔 - 설정정보 없이도 자동으로 스프링 빈 등록하는 기능 + Autowired</li>
</ol>
</li>
<li>장점<ol>
<li>의존성이 줄어든다: 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다</li>
<li>정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다</li>
</ol>
</li>
</ol>
<h2 id="2-주입-방법">2. 주입 방법</h2>
<h3 id="생성자-주입-권장">생성자 주입 (권장)</h3>
<p>말 그대로 생성자를 통해 주입</p>
<p>인스턴스 선언 시에는 final 붙이고, 생성자에서 주입해줌 (생성자에 @Autowired 붙음)</p>
<p>장점: final 때문에 불변성 보장</p>
<h3 id="수정자-주입">수정자 주입</h3>
<p>setter 같은 수정자 통해 주입 (마찬가지로 setter에 @Autowired 붙음)</p>
<p>선택, 변경 가능성이 있는 의존관계에서 사용 </p>
<h3 id="필드-주입">필드 주입</h3>
<p>인스턴스 선언시 거기에 @Autowired붙임</p>
<p>코드는 간결하지만 외부에서 변경이 불가능하기 때문에 테스트하기 힘들다는 치명적 단점</p>
<p>사용하지마라~!</p>
<h3 id="일반-메서드-주입">일반 메서드 주입</h3>
<p>말 그래도 일반 메서드 주입</p>
<p>잘 안 씀</p>
<h2 id="3-구현-방법">3. 구현 방법</h2>
<h3 id="3-b-configuration-사용-예제-코드">3-b. @Configuration 사용 예제 코드</h3>
<p>이론을 이해하기 위해 작성했고, 현재는 컴포넌트 스캔을 더 자주 쓰는 것 같음</p>
<pre><code class="language-java">public interface SpeakingLanguage {
    String introduceSelf();
}

public class French implements SpeakingLanguage {
    @Override
    public String introduceSelf() {
        return &quot;Salut Je parle français&quot;;
    }
}

public class English implements SpeakingLanguage {
    @Override
    public String introduceSelf() {
        return &quot;Hi I speak English&quot;;
    }
}</code></pre>
<pre><code class="language-java">@Configuration
public class LanguageConfig {
    @Bean
    public SpeakingLanguage speakingLanguage() { return new French(); }
}</code></pre>
<pre><code class="language-java">public class LanguageTest {

    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(LanguageConfig.class);
    SpeakingLanguage speakingLanguage = applicationContext.getBean(&quot;speakingLanguage&quot;, SpeakingLanguage.class);

    @Test
    public void 현재_설정된_언어(){
        System.out.println(speakingLanguage.introduceSelf());
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/21a8d2c9-0321-4cee-9d20-4453399947c5/image.png" alt=""></p>
<ol>
<li>@Configuration<ol>
<li>스프링 컨테이너 사용</li>
<li>@Configuration이 붙은 파일을 설정 정보로 사용. 여기에 @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록 → 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 함</li>
<li>스프링 빈은 메서드의 명을 스프링 빈 이름으로 사용함</li>
</ol>
</li>
<li>언어를 바꾸고싶다면 설정정보 파일에서만 바꾸면 됨<ol>
<li>의존성 주입을 이용하면 의존관계 변경을 위한 수정이 필요가 없다고하던데 어쨌든 코드에 변경이 있는거 아니냐는 의문이 길게 남았다. 근데 이건 설정 정보 파일에서만 수정이 일어나고 클라이언트 코드인 테스트코드나 language인터페이스에서는 수정이 없기 때문에 수정이 없다고 보는 것. 클라이언트 코드 입장에서 하위 코드의 수정이 있다는 걸 몰라도 된다면 수정이 없는 거다</li>
</ol>
</li>
</ol>
<h3 id="3-c-component-컴포넌트-스캔-사용-예제-코드">3-c. @Component (컴포넌트 스캔) 사용 예제 코드</h3>
<pre><code class="language-java">public interface SpeakingLanguage {
    String introduceSelf();
}

@Component
@Primary
public class French implements SpeakingLanguage {
    @Override
    public String introduceSelf() {
        return &quot;Salut Je parle français&quot;;
    }
}

@Component
public class English implements SpeakingLanguage {
    @Override
    public String introduceSelf() {
        return &quot;Hi I speak English&quot;;
    }
}</code></pre>
<pre><code class="language-java">@SpringBootTest
public class LanguageTest {
    @Autowired
    private SpeakingLanguage speakingLanguage;

    @Test
    public void 현재_설정된_언어(){
        System.out.println(speakingLanguage.introduceSelf());
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/p_l_colline/post/e4f13ffd-40d9-4d99-a544-61f79230d9df/image.png" alt=""></p>
<ol>
<li>인터페이스에 configuration을 붙이는 대신 하위 구현체들에 @Component를 붙임<ol>
<li>해당 애노테이션 추가하면 컴포넌트 스캔의 대상이 된다</li>
</ol>
</li>
<li>Autowired는 의존관계를 자동으로 주입해준다<ol>
<li>룰을 간단하게만 설명하자면<ol>
<li>먼저 타입 매칭을 시도한 후</li>
<li>이때 찾아진 빈이 여러개라면 필드 이름으로 매칭</li>
<li>또 여러개라면 파라미터 이름으로 매칭한다</li>
<li>다른 것들 더 있는데 여기서는 패스</li>
</ol>
</li>
</ol>
</li>
</ol>
<h1 id="내가-느낀-di의-장점">내가 느낀 DI의 장점?</h1>
<ol>
<li>프로젝트 시작 전에 사용할 인터페이스를 정의한다. 인터페이스가 약속되고나면 팀원 각각은 해당 클래스가 어떻게 구현되는지는 신경쓰지 않아도 되기 때문에<ol>
<li>개발 속도가 빨라진다. </li>
<li>테스트 코드 작성에 용이하다.</li>
</ol>
</li>
<li>어떠한 클래스의 인스턴스를 생성할 때 해당 클래스의 생성자가 어떤식으로 정의되어있는지 신경쓰지 않아도 된다.</li>
</ol>
<h3 id="개인적인-정리">개인적인 정리</h3>
<p>DI와 객체지향을 정리하다보니 둘의 장점이 구분하기 힘들다고 느꼈다. 생각해보니 DI는 객체지향의 장점을 더 극대화 시킨 방식이라고 느꼈다. 기본적인 의미에서의 객체지향에서는 해당 클래스가 구체적으로 어떻게 구현되어있는지 몰라도 쓸 수 있었지만, 어떻게 생성하는지는 알아야한다. 하지만 DI를 사용한다면 구체적 구현 뿐만 아니라 생성자의 형태도 몰라도 된다. 라고 느꼈다.</p>
<pre><code class="language-java">public class NotificationService {
    private EmailService emailService = new EmailService(); // 강한 결합

    public void sendNotification(String message) {
        emailService.sendEmail(message);
    }
}

public class NotificationService {
    private final EmailService emailService;

    @Autowired
    public NotificationService(EmailService emailService) {
        this.emailService = emailService; // DI를 통해 주입
    }

    public void sendNotification(String message) {
        emailService.sendEmail(message);
    }
}</code></pre>
<p>상위코드가 단순히 객체지향만을 생각했을 때고</p>
<p>하위코드가 DI까지 사용했을 때의 코드이다</p>
<p>현재는 생성자에 따로 넘겨줄 인자가 없기 때문에 비슷해 보이지만, 생성자에 넘겨줘야할 정보가 있다고 생각하면 차이점이 좀 더 잘 보일 것이다.</p>
<hr>
<p>되돌아보니 아는 게 제대로 없다는 생각에서 시작된 바닥부터 다시 공부하기</p>
<p>하면 할 수록 자괴감들지만 언젠가 이게 밑거름이 되길 바라며</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체지향 프로그래밍 vs 절차지향 프로그래밍 - Java, Cpp, python 중심으로]]></title>
            <link>https://velog.io/@p_l_colline/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-vs-%EC%A0%88%EC%B0%A8%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-Java-Cpp-python-%EC%A4%91%EC%8B%AC%EC%9C%BC%EB%A1%9C</link>
            <guid>https://velog.io/@p_l_colline/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-vs-%EC%A0%88%EC%B0%A8%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-Java-Cpp-python-%EC%A4%91%EC%8B%AC%EC%9C%BC%EB%A1%9C</guid>
            <pubDate>Sat, 21 Sep 2024 06:03:17 GMT</pubDate>
            <description><![CDATA[<h1 id="절차지향">절차지향</h1>
<ol>
<li>정의<ol>
<li>프로그램의 순서와 흐름을 먼저 세우고, 필요한 자료구조와 함수를 설계</li>
<li>어떤 기능을 어떤 순서로 처리할 것인가? (함수 호출의 개념)</li>
<li>데이터를 중심으로 함수를 구현</li>
</ol>
</li>
<li>종류<ol>
<li>c, fortran, pascal</li>
</ol>
</li>
</ol>
<h1 id="객체지향">객체지향</h1>
<ol>
<li>정의<ol>
<li>자료구조와 이를 중심으로 한 모듈들을 먼저 설계하고, 이들의 실행순서와 흐름을 짜는 방식</li>
<li>실생활에 쓰는 모든 것을 객체라고 함, 프로그램 구현에 필요한 객체를 파악하고 각각의 객체들의 역할이 무엇인지를 정의하여 객체 간의 상호작용을 통해 프로그램을 만듦</li>
<li>기능을 중심으로 메소드 구현</li>
</ol>
</li>
<li>종류<ol>
<li>C++, C#, Java, Python</li>
</ol>
</li>
<li>장점<ol>
<li>상속, 캡슐화, 다형성의 특징으로 코드의 재사용성, 확장성이 높다</li>
<li>객체의 상호작용을 생각하기 때문에 실세계에 대한 모델링이 좀 더 쉽다</li>
<li>캡슐화로 인해, 실제로 구현되는 부분은 외부에 드러나지 않도록 은닉하여 보안성이 높다</li>
</ol>
</li>
<li>단점<ol>
<li>캡슐화와 격리구조 때문에 절차지향 프로그래밍보다 실행속도가 느리다</li>
</ol>
</li>
<li>특징<ol>
<li>추상화</li>
<li>캡슐화</li>
<li>상속성</li>
<li>다형성</li>
</ol>
</li>
</ol>
<h1 id="예시를-통한-비교">예시를 통한 비교</h1>
<h2 id="객체-지향-언어-java를-이용한-예제">객체 지향 언어 Java를 이용한 예제</h2>
<pre><code class="language-java">// 동물의 공통적인 행동을 정의한 Animal 클래스
public class Animal {
    public void sound() {
        System.out.println(&quot;Some generic animal sound&quot;);
    }
}

// 개라는 구체적인 동물에 대한 Dog 클래스
public class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println(&quot;Bark&quot;);
    }
}

// 고양이라는 구체적인 동물에 대한 Cat 클래스
public class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println(&quot;Meow&quot;);
    }
}

// 동물들이 소리를 내는 동작을 테스트하는 코드
public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();  // Dog 객체 생성
        Animal myCat = new Cat();  // Cat 객체 생성

        myDog.sound();  // 출력: Bark
        myCat.sound();  // 출력: Meow
    }
}</code></pre>
<h2 id="객체-지향-언어-c를-이용한-예제">객체 지향 언어 C++를 이용한 예제</h2>
<pre><code class="language-java">#include &lt;iostream&gt;
#include &lt;string&gt;
using namespace std;

// Animal 클래스: 동물의 공통적인 동작을 정의
class Animal {
public:
    virtual void sound() {
        cout &lt;&lt; &quot;Some generic animal sound&quot; &lt;&lt; endl;
    }
};

// Dog 클래스: Animal을 상속하고 sound() 메서드를 재정의
class Dog : public Animal {
public:
    void sound() override {
        cout &lt;&lt; &quot;Bark&quot; &lt;&lt; endl;
    }
};

// Cat 클래스: Animal을 상속하고 sound() 메서드를 재정의
class Cat : public Animal {
public:
    void sound() override {
        cout &lt;&lt; &quot;Meow&quot; &lt;&lt; endl;
    }
};

// 메인 함수: 다형성을 활용하여 동물의 소리를 출력
int main() {
    Animal* myDog = new Dog();  // Dog 객체 생성
    Animal* myCat = new Cat();  // Cat 객체 생성

    myDog-&gt;sound();  // 출력: Bark
    myCat-&gt;sound();  // 출력: Meow

    // 메모리 해제
    delete myDog;
    delete myCat;

    return 0;
}</code></pre>
<ul>
<li>자바와 씨쁠쁠의 차이</li>
</ul>
<ol>
<li><strong>메모리 관리</strong>: Java는 가비지 컬렉션을 사용하여 메모리를 자동으로 관리하지만, C++에서는 new와 delete를 통해 수동으로 메모리 관리를 해야 합니다.</li>
<li><strong>다형성 구현</strong>: Java에서는 모든 메서드가 기본적으로 가상 메서드(virtual method)이지만, C++에서는 virtual 키워드를 명시해야 다형성을 지원합니다.</li>
</ol>
<h2 id="절차지향-언어-c를-이용한-예제">절차지향 언어 c를 이용한 예제</h2>
<pre><code class="language-java">#include &lt;stdio.h&gt;

// 개의 소리를 내는 함수
void dogSound() {
    printf(&quot;Bark\n&quot;);
}

// 고양이의 소리를 내는 함수
void catSound() {
    printf(&quot;Meow\n&quot;);
}

// 메인 함수에서 각각의 동물 소리를 직접 호출
int main() {
    dogSound();  // 출력: Bark
    catSound();  // 출력: Meow
    return 0;
}</code></pre>
<h1 id="표로-정리">표로 정리</h1>
<table>
<thead>
<tr>
<th></th>
<th>객체지향 Java</th>
<th>객체지향 C++</th>
<th>절차지향 c</th>
</tr>
</thead>
<tbody><tr>
<td>1. 클래스</td>
<td>O</td>
<td>O</td>
<td>X (포인터로 흉내 가능)</td>
</tr>
<tr>
<td>2. 다형성</td>
<td>O</td>
<td>O</td>
<td>X (포인터로 흉내 가능)</td>
</tr>
<tr>
<td>1. 클래스</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>1. 객체지향: 클래스라는 개념이 존재하므로, 공통적인 행동을 Animal에 정의하고, 구체적인 동물 별 특성은 Animal클래스를 상속받은 하위 클래스에서 정의 가능</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>2. 절차지향: 모든 동물들이 공통적으로 갖는 특성을 묶어서 정의를 할 수가 없음 → 코드 반복</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>2. 다형성</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>1. 같은 인터페이스나 상위 클래스를 통해 여러 다른 타입의 객체를 동일하게 다룰 수 있게 해주는 특성</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>2. 클래스 상속, 인터페이스(혹은 추상 클래스), 메서드 오버라이딩, 동적 바인딩을 통해 구현됨</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>3. 동적 바인딩</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>1. 런타임 시점에 객체의 실제 타입을 결정하여 해당 타입의 메서드를 호출하는 것</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h1 id="내가-프로젝트를-하면서-느낀-객체지향의-장점">내가 프로젝트를 하면서 느낀 객체지향의 장점?</h1>
<ol>
<li>service와 serviceImpl<ol>
<li>인터페이스에 로직을 정의하고, 구현체에 해당 로직을 구체적으로 구현함. 이로써 클라이언트 코드는 인터페이스를 참조해서 원하는 로직만 가져갈 수 있음. 해당 로직이 구체적으로 어떻게 구현되어있는지는 신경을 쓰지 않아도 됨 → 구체적 구현이 달라지더라도 클라이언트 코드에서는 신경쓰지 않아도 됨</li>
</ol>
</li>
<li>같은 특성은 한 번만 정의해도 됨<ol>
<li>예를 들어 오피스텔과 주택이라는 클래스를 정의한다. 이때 두 클래스 모두가 공통적으로 갖고있어야하는 변수는 ‘주소’가 있을 것이다. 이때 집이라는 인터페이스를 선언해서 거기에 주소라는 변수를 넣어둔다. 이러면 같은 코드를 여러번 반복해서 쓰지 않아도 되기 때문에 유지보수성이 높다. 더불어 추후에 아파트라는 클래스를 추가할 때도 집 인터페이스를 상속받으면 되므로 확장성도 확보.</li>
</ol>
</li>
</ol>
<hr>
<p>참고</p>
<p><a href="https://blackvill.tistory.com/221">https://blackvill.tistory.com/221</a></p>
<p><a href="https://velog.io/@goblin820/%EC%A0%88%EC%B0%A8%EC%A7%80%ED%96%A5-%EB%B0%8F-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D">https://velog.io/@goblin820/절차지향-및-객체지향-프로그래밍</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DTO design]]></title>
            <link>https://velog.io/@p_l_colline/DTO-design</link>
            <guid>https://velog.io/@p_l_colline/DTO-design</guid>
            <pubDate>Fri, 06 Sep 2024 01:50:40 GMT</pubDate>
            <description><![CDATA[<h2 id="dto와-entity-간의-변환">DTO와 Entity 간의 변환</h2>
<p>DTO -&gt; Entity 혹은 Entity -&gt; DTO의 변환과정은 어떻게 될까. 많이 사용하는 방법은 두 가지이다.</p>
<ol>
<li>controller에서 변환
 -&gt; controller와 domain이 결합하여 domain을 변경하면 controller도 연쇄적으로 변경해야할 수도 있음</li>
<li>service에서 변환
 -&gt; controller와 service의 결합도 높아짐</li>
<li>Mapper
 -&gt; controller와 service 사이에 위치하여 사이에서 변환을 담당</li>
</ol>
<h2 id="dto-design">DTO design</h2>
<h3 id="1-필요에-따라-구성">1. 필요에 따라 구성</h3>
<h4 id="requestdto">RequestDTO</h4>
<p>클라이언트로부터 전달받은 요청 데이터를 담은 객체
@RequestParam으로 데이터를 하나씩 받을 필요없이 객체로 한 번에 받을 수 있음
엔티티 캡슐화 -&gt; 엔티티의 값이 변경되지 않도록 함</p>
<h4 id="responsedto">ResponseDTO</h4>
<p>비즈니스 로직에서 생성된 데이터를 클라이언트에 반환하기 위한 객체 (컨트롤러에서 생성되어 클라이언트에 반환)
꼭 필요로하는 데이터만 넘길 수 있음
엔티티 캡슐화
클래스 작성 할 때, 화면에 필수적으로 보여져야하는 데이터가 뭔지 생각해보면 좀 명쾌할지도</p>
<h4 id="단점">단점:</h4>
<p>하지만 단순히 DTO 패키지에 필요할 때마다 새로운 class를 생성하면 파일이 너무 많아진다
    -&gt; 알아보기 힘들다. 이에 대한 해결책을 아래에 기술한다.</p>
<h3 id="2-inner-class-혹은-nested-class">2. Inner class 혹은 Nested class</h3>
<h4 id="장점">장점:</h4>
<p>깔끔한 패키지
DTO className정하기 수월해짐</p>
<h4 id="inner-class에-static을-붙이는-이유">inner class에 static을 붙이는 이유</h4>
<p>static이 붙지않은 inner class는 같은 상위 클래스에 포함되어있는 다른 inner class에 접근이 가능하다. 해당 클래스가 private이어도 가능하다.
반면 static이 붙은 inner class는 다른 클래스의 멤버에 접근할 수 없다. (outer class들에는 가능)</p>
<h4 id="단점-1">단점:</h4>
<p>클래스의 개수는 줄어들었겠지만 새로운 클래스가 필요할 때마다 재생성 해야함
DTO 재사용 불가</p>
<p>참고</p>
<ol>
<li><a href="https://velog.io/@jihoon-gh/Dto%EC%99%80-Entity%EC%9D%98-%EB%B3%80%ED%99%98%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EB%AF%BC%EA%B3%BC-Mapper%EC%9D%98-%EC%82%AC%EC%9A%A9">https://velog.io/@jihoon-gh/Dto%EC%99%80-Entity%EC%9D%98-%EB%B3%80%ED%99%98%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EB%AF%BC%EA%B3%BC-Mapper%EC%9D%98-%EC%82%AC%EC%9A%A9</a></li>
<li><a href="https://lackofwillpower.tistory.com/166">https://lackofwillpower.tistory.com/166</a></li>
<li></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[DTO의 기본 정보]]></title>
            <link>https://velog.io/@p_l_colline/DTO</link>
            <guid>https://velog.io/@p_l_colline/DTO</guid>
            <pubDate>Thu, 05 Sep 2024 05:27:55 GMT</pubDate>
            <description><![CDATA[<h2 id="정의-및-장점">정의 및 장점</h2>
<p>Data transfer Object 
주로 데이터 전송에 사용되는 객체
엔티티는 DB에 저장-관리되는 데이터를 의미, DTO는 비즈니스 로직에서 사용되는 데이터 전송 객체
service와 controller에서 원하는 데이터의 종류가 다른 경우 각 계층에서 필요로하는 것들만 DTO에서 묶어서 각 계층에 전달할 수 있다</p>
<ol>
<li>서버와의 통신에서 네트워크 오버헤드를 줄여준다 </li>
<li>encapsulation of the serialization&#39;s logic</li>
</ol>
<h2 id="사용">사용</h2>
<p>POJO를 주로 만든다 (Plain Old Java Object- flat data structure that contain no business logic)
주로 domain model에서 DTO로 매핑한다 - mapper component (presentation 또는 facade layer에 있는)를 통해 됨 - DTO와 domain model이 서로를 알 필요가 없다</p>
<h2 id="사용시-일반적-실수">사용시 일반적 실수</h2>
<ol>
<li>create different DTOs for every occasion
 -&gt; 클래스와 매퍼의 개수를 늘려 유지보수 힘들게 됨</li>
<li>use a single class for many senarios
 -&gt; 너무 많은 연결 (높은 의존성)은 유지보수 힘듦, 객체지향 어긋남</li>
<li>add business logic to those classes
 -&gt; 비지니스 로직은 domain layer에 위치해야함</li>
<li>LocalDTOs, where DTOs pass data across domains
 -&gt; 매퍼들을 위한 비용 높아짐</li>
</ol>
<p>참고</p>
<ol>
<li><a href="https://cobi-98.tistory.com/61#google_vignette">https://cobi-98.tistory.com/61#google_vignette</a></li>
<li><a href="https://www.baeldung.com/java-dto-pattern">https://www.baeldung.com/java-dto-pattern</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[backend naming convention]]></title>
            <link>https://velog.io/@p_l_colline/spring-naming-convention</link>
            <guid>https://velog.io/@p_l_colline/spring-naming-convention</guid>
            <pubDate>Thu, 05 Sep 2024 02:11:57 GMT</pubDate>
            <description><![CDATA[<h1 id="naming-methods-in-each-layer">naming methods in each layer</h1>
<h4 id="controller-layer">controller layer</h4>
<p>HTTP method에 기반해 네이밍 되어야 함
ex: getUser, postUser, deleteUser</p>
<h4 id="service-layer">service Layer</h4>
<p>기능을 나타내야하고, 동사가 접두어로 사용되어야 함
ex: createUser, updateUser, deleteUser</p>
<h4 id="persistence-layer">persistence layer</h4>
<p>데이터 오퍼레이션을 보여줘야 함
ex: save, findById, findAll, delete</p>
<h1 id="참고">참고</h1>
<h4 id="네이밍">네이밍</h4>
<ol>
<li><a href="https://cocobi.tistory.com/27">https://cocobi.tistory.com/27</a></li>
<li><a href="https://m-falcon.tistory.com/673">https://m-falcon.tistory.com/673</a><h4 id="퍼시스턴스-계층">퍼시스턴스 계층</h4>
</li>
<li><a href="https://blog.naver.com/nine01223/220373593398">https://blog.naver.com/nine01223/220373593398</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[interface naming convention (find / get)]]></title>
            <link>https://velog.io/@p_l_colline/interface-naming-convention-find-get</link>
            <guid>https://velog.io/@p_l_colline/interface-naming-convention-find-get</guid>
            <pubDate>Thu, 05 Sep 2024 01:43:45 GMT</pubDate>
            <description><![CDATA[<h1 id="find">find</h1>
<ol>
<li>찾는 내용이 없을 수도 있다</li>
<li>찾는 내용이 없으면 없다고 알려줌</li>
<li>주로 Optional로 감싼 인스턴스 반환</li>
<li>ex: 서점 사이트에서 하나의 특정한 책을 검색하는데 없을 수도 있잖아? 이때 find</li>
</ol>
<h1 id="get">get</h1>
<ol>
<li>찾는 내용이 반드시 있다</li>
<li>찾는 내용이 없다면 exception 날림</li>
<li>ex: 서점 주문 내역을 보는데 이때는 반드시 주문한 책이 존재해야하는 거잖아? 이때 get</li>
<li>getOne은 deprecated됨 대신 getReferenceById 권장</li>
</ol>
<h1 id="주의점">주의점</h1>
<ol>
<li>null을 날리지마라. 무조건 의미있는 return</li>
</ol>
<p>참고</p>
<ol>
<li><a href="https://tuhrig.de/find-vs-get/">https://tuhrig.de/find-vs-get/</a></li>
<li><a href="https://creampuffy.tistory.com/m/162">https://creampuffy.tistory.com/m/162</a></li>
</ol>
]]></description>
        </item>
    </channel>
</rss>