<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>solved_err.log</title>
        <link>https://velog.io/</link>
        <description>배우고 기록하는 개발 일기장✍</description>
        <lastBuildDate>Tue, 14 Mar 2023 00:43:18 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>solved_err.log</title>
            <url>https://velog.velcdn.com/images/so-eun/profile/0f562d16-8598-4bcc-b3a7-3453c07757d3/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. solved_err.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/so-eun" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Python] 코루틴(Coroutine) - 네이티브 코루틴과 제너레이터 기반 코루틴]]></title>
            <link>https://velog.io/@so-eun/python-%EC%BD%94%EB%A3%A8%ED%8B%B4Coroutine%EA%B3%BC-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0Generator-%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0Iterator</link>
            <guid>https://velog.io/@so-eun/python-%EC%BD%94%EB%A3%A8%ED%8B%B4Coroutine%EA%B3%BC-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0Generator-%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0Iterator</guid>
            <pubDate>Tue, 14 Mar 2023 00:43:18 GMT</pubDate>
            <description><![CDATA[<p>파이썬을 이용해 비동기 파트를 다루면서 다음 용어에 대해 많이 접하게 되었다.
한 번쯤 들어봤을 코루틴, 제너레이터와 이터레이터에 대해 정리해보았다.</p>
<hr>
<h2 id="코루틴coroutine">코루틴(Coroutine)</h2>
<h3 id="네이티브-코루틴native-coroutine">네이티브 코루틴(Native Coroutine)</h3>
<p>파이썬에서 여러 작업을 동시에 병렬 처리하기 위해 비동기(Asynchronous) 함수를 사용한다.</p>
<pre><code class="language-python">async def cr_func():
    print(&#39;test&#39;)</code></pre>
<p>이렇게 async 키워드로 만든 비동기 함수를 코루틴(Coroutine) 또는 코루틴 함수라고 한다. 그렇다면, 이 코루틴 함수는 언제 어떻게 사용하는 것일까?</p>
<pre><code class="language-python">async def cr_func():
    print(&#39;test&#39;)

cr_func()


### ----- Result ----- ###
RuntimeWarning: coroutine &#39;cr_func&#39; was never awaited
### ------------------- ###</code></pre>
<p>cr_func 이라는 이름의 코루틴 함수를 선언하고 호출하자 다음과 같은 에러가 나타났다.
async 키워드로 선언된 코루틴 함수는 일반 함수 호출 방식과는 다르며, 다음과 같이 실행한다.</p>
<pre><code class="language-python">import asyncio

async def cr_func():  # 코루틴 함수 선언
    print(&#39;Coroutine Function!&#39;)

loop = asyncio.get_event_loop()       # asyncio 모듈의 event loop 객체 생성
loop.run_until_complete(cr_func())  # cr_func 함수가 완전히 종료될 때까지 대기
loop.close()                          # event loop 종료 


### ----- Result ----- ###
Coroutine Function!
### ------------------- ###</code></pre>
<blockquote>
<p>📌 <strong>asyncio</strong> 모듈의 <strong>get_event_loop</strong>를 얻어와서 <strong>run_until_complete</strong>의 인자로 코루틴 함수를 전달하여 실행한다. 
run_until_complete 함수는 인자로 받은 함수가 완전히 종료될 때까지 이벤트 루프를 실행하게 된다.
이와 같이, async로 선언된 코루틴을 <span style="color:purple;">&quot;<strong>네이티브 코루틴</strong>&quot; </span>이라고 한다.</p>
</blockquote>
<p>다른 함수에서 이 코루틴 함수를 호출할 때는 어떻게 할까?</p>
<pre><code class="language-python">import asyncio

async def cr_func():
    print(&#39;test&#39;)

async def main():
    await cr_func()

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()</code></pre>
<p>코루틴 함수 생성 방식은 동일하나, 코루틴 함수를 호출하고자 하는 함수에서 <strong>await</strong> 키워드를 붙여줘야 한다.
await = 말 그대로 <span style="text-decoration:underline;">cr_func()함수가 완료되기를 기다린 후 결과를 가져온다</span>는 의미이다.</p>
<p><span style="color:darkblue;font-weight:bold">응답을 받을 때까지 아무것도 하지 않고 기다린다는 것이 아니라 해당 함수가 완료될 때까지 다른 작업을 하면서 완료를 기다린다는 의미이다.</span></p>
<h3 id="제너레이터-기반-코루틴generator-based-coroutine">제너레이터 기반 코루틴(Generator-based Coroutine)</h3>
<p>일반적으로 사용하는 네이티브 코루틴 외의 또 다른 방식인 제너레이터 기반 코루틴에 대해 정리해보았다.</p>
<p><a href="https://velog.io/@so-eun/python-%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0Iterator%EC%99%80-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0Generator">제너레이터에 대해서 정리한 글</a></p>
<p>다음은 <code>yield</code> 키워드를 통해 메인 루틴과 서브 루틴간에 값을 이동하며 실행의 지연 및 재개를 보여주는 예제이다.</p>
<pre><code class="language-python">def cr_gen():
    total = 0
    while True:    # 코루틴을 계속 유지하기 위해 무한 루프 사용
        num = yield total # total 발생 및 num에 값 저장
        total += num

cr = cr_gen()
print(next(cr)) # 처음 yield 까지 실행
print(cr.send(3)) # num에 3 저장 후 다음 yield 까지 실행
print(cr.send(2)) # num에 2 저장 후 다음 yield 까지 실행

&quot;&quot;&quot; 실행결과
0
3
5
&quot;&quot;&quot;</code></pre>
<blockquote>
<p>📌이와 같이 코루틴 바깥에서 보낸 값을 <strong>yield 변수</strong>(여기서는 num)에 받아 저장한 후, 다음 루틴을 실행한다.
정리하면, 핵심은 &quot;<strong>코루틴은 yield에서 함수 중간에 대기한 다음에 메인 루틴을 실행하다가 다시 코루틴을 실행한다</strong>&quot;는 것이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Python] 이터레이터(Iterator)와 제너레이터(Generator) ]]></title>
            <link>https://velog.io/@so-eun/python-%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0Iterator%EC%99%80-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0Generator</link>
            <guid>https://velog.io/@so-eun/python-%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0Iterator%EC%99%80-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0Generator</guid>
            <pubDate>Mon, 13 Mar 2023 07:48:44 GMT</pubDate>
            <description><![CDATA[<p>파이썬에서 자주 접할 수 있는 용어인 이터레이터와 제너레이터에 대해 정리해보았다.</p>
<h3 id="이터레이터iterator">이터레이터(Iterator)</h3>
<p>iterable한 객체를 내장 함수 또는 iterable객체의 메소드로 객체를 생성할 수 있다. 그렇다면 iterable한 객체란 무엇일까?
iterable, <strong>반복 가능한</strong> 객체이다. 순서대로 다음 값을 리턴할 수 있는 객체를 의미하며, 파이썬에서 대표적으로 iterable한 타입은 list, dict, set, str, bytes, tuple, range 등이 있다.
예를 들어, list 객체는 다음과 같이 반복문을 통해 값을 사용할 수 있다. 이렇게 내부 요소를 하나씩 리턴할 수 있는 요소를 iterable한 객체라고 할 수 있다.</p>
<pre><code class="language-python">a = [1, 2, 3]

for item in a:
    print(item)</code></pre>
<p>iter함수를 통해 iterable 객체를 iterator로 만들 수 있으며, iterator로 변경 후에 <code>__next__()</code> 함수 호출이 가능하다.</p>
<pre><code class="language-python"># 1. iter() 함수 사용 전
print(a.__next__) # AttributeError 발생


# 2. iter() 함수 사용 후
a = iter(a)
#a = [1, 2, 3].__iter__()     # 같은 결과

print(a.__next__()) # 1 출력
print(a.__next__()) # 2 출력
print(a.__next__()) # 3 출력
print(a.__next__()) # StopIteration Exception</code></pre>
<p><code>__next__()</code> 함수를 호출해서 값을 반복적으로 꺼내 쓸 수 있고, 마지막 값까지 모두 꺼낸 후에는 StopIteration 예외가 발생한다.</p>
<h3 id="제너레이터generator">제너레이터(Generator)</h3>
<p>iterator를 생성해주는 함수이며, 함수안에 <code>yield</code> 키워드를 사용한다.
yield가 호출되면 암시적으로 return이 호출되며, 한번 더 실행되면 실행되었던 &#39;yield&#39; 다음 코드가 실행된다. 다음의 예제를 확인해보자.</p>
<pre><code class="language-python">def simple_gen():
    yield &quot;Hello&quot;
    yield &quot;World&quot;

gen = simple_gen()
print(gen)             # Hello 출력
print(next(gen))    # World 출력
print(next(gen))    # StopIteration Exception</code></pre>
<p>처음 gen을 호출할 때에는 simple_gen()함수를 실행하다가 yield 키워드를 만나면서 &quot;Hello&quot; 값을 return하고 해당 위치를 기억한다.
이후, next함수를 통해 다시 함수가 호출되면 앞서 return한 이후부터 코드를 실행하고 yield 키워드를 만나면 또 다시 &quot;World&quot; 값을 return한다.  </p>
<p>이렇게 생성한 <span style="color:purple;font-weight:bold;">genertaor는 iterable한 객체가 되며</span> for문에서 사용할 수 있게된다.</p>
<pre><code class="language-python">def list_generator():
    a = [1, 2, 3]
    for i in a:
        yield i        #한번씩 값을 바깥으로 전달

gen = list_generator()
#case 1.
print(list(gen))    # [1, 2, 3] 출력
#------------------------------

#case 2.
print(next(gen))    # 1 출력
print(next(gen))    # 2 출력
print(next(gen))    # 3 출력
#------------------------------</code></pre>
<p>이와 같이, 제너레이터는 함수를 끝내지 않은 상태에서 yield를 사용해 값을 바깥으로 전달할 수 있다.
return은 반환 즉시 함수가 끝나지만 yield는 잠시 함수 바깥의 코드가 실행되도록 주도권을 양보하여 값을 가져가게 한 뒤 다시 제너레이터 안의 코드를 계속 실행하는 방식이다. </p>
<p>*<em>모든 제너레이터는 이터레이터에 포함된다고 할 수 있다. *</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kafka] python으로 kafka 실행하기]]></title>
            <link>https://velog.io/@so-eun/kafka-python%EC%9C%BC%EB%A1%9C-kafka-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@so-eun/kafka-python%EC%9C%BC%EB%A1%9C-kafka-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 25 Jan 2023 07:31:43 GMT</pubDate>
            <description><![CDATA[<p>이전 내용에 이어 이번에는 실제 파이썬 코드를 통해 카프카를 실행해보자.
이번에는 로컬에서 파이썬 코드로 테이블에 데이터를 insert한다.</p>
<h3 id="1-파이썬-카프카-라이브러리-설치">1. 파이썬-카프카 라이브러리 설치</h3>
<p>먼저, 파이썬에서 카프카를 사용하기 위해 라이브러리를 설치한다.</p>
<pre><code class="language-bash">pip install kafka-python</code></pre>
<br>

<h3 id="2-producer-생성">2. producer 생성</h3>
<p>토픽으로 데이터를 전송할 프로듀서를 생성한다.
여기서는 <em>new-topic.txt</em> 파일의 값을 보내는 방식으로 적용한다.
<span style="color:#8B0000">(참고: 나는 카프카 테스트를 <strong>프로젝트 루트의 test 폴더</strong>에서 작업하였다.) </span></p>
<blockquote>
<p>👀 브로커를 &#39;localhost:9092&#39;라고 세팅해주었으니, 당연히 <span style="color:orange"><em>로컬에서 zookeeper와 kafka를 실행</em></span>한 상태에서 진행한다. 
로컬에서 zookeeper와 kafka 실행하는 방법은 <a href="https://velog.io/@so-eun/Kafka-%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%A1%9C%EC%BB%AC-%EC%97%90%EC%84%9C-%EC%B9%B4%ED%94%84%EC%B9%B4-%EC%8B%A4%ED%96%89%ED%95%B4%EB%B3%B4%EA%B8%B0">이전 게시글</a>을 참고한다.</p>
</blockquote>
<pre><code class="language-python">  # 전송할 데이터 파일
  # test/new-topic.txt

  -----start-----
  1
  2
  3
  ..
  ..
  9998
  9999
  10000
  -----end-----</code></pre>
<pre><code class="language-python"># test/producer.py

from kafka import KafkaProducer
import json
import time
from csv import reader


class MessageProducer:
    broker = &quot;&quot;
    topic = &quot;&quot;
    producer = None

    def __init__(self, broker, topic):
        self.broker = broker
        self.topic = topic
        self.producer = KafkaProducer(bootstrap_servers=self.broker,
                                      value_serializer=lambda x: json.dumps(x).encode(&#39;utf-8&#39;),
                                      acks=0,
                                      api_version=(2,5,0),
                                      retries=3
                                      )

    def send_message(self, msg):
        try:
            future = self.producer.send(self.topic, msg)
            self.producer.flush()   # 비우는 작업
            future.get(timeout=60)
            return {&#39;status_code&#39;: 200, &#39;error&#39;: None}
        except Exception as e:
            print(&quot;error:::::&quot;,e)
            return e

# 브로커와 토픽명을 지정한다.
broker = &#39;localhost:9092&#39;
topic = &#39;new-topic&#39;
message_producer = MessageProducer(broker, topic)

with open(&#39;test/&#39;+topic+&#39;.txt&#39;, &#39;r&#39;, encoding=&#39;utf-8&#39;) as file:
    for data in file:
        print(&quot;send-data: &quot;, data)
        res = message_producer.send_message(data)
</code></pre>
<br>

<h3 id="3-consumer-생성-가져온-데이터-처리테이블-insert">3. consumer 생성, 가져온 데이터 처리(테이블 insert)</h3>
<p>토픽으로부터 데이터를 가져올 컨슈머를 생성하고, 컨슈머에서 데이터 처리를 한다.
수집한 데이터는 <strong>TblKafkaTest</strong> 테이블에 row 단위로 insert 한다.
테이블은 임시로 다음과 같이 생성하였다.</p>
<pre><code class="language-python"># test/models.py
from django.db import models

class TblKafkaTest(models.Model):
    idx = models.BigAutoField(primary_key=True)
    name = models.CharField(max_length=50, blank=True, null=True)
    col_a = models.CharField(max_length=50, blank=True, null=True)
    col_b = models.CharField(max_length=50, blank=True, null=True)
    col_c = models.CharField(max_length=50, blank=True, null=True)
    col_d = models.CharField(max_length=50, blank=True, null=True)
    create_date = models.DateTimeField()

    class Meta:
        managed = False
        db_table = &quot;tbl_kafka_test&quot;</code></pre>
<hr>
<pre><code class="language-python"># test/consumer.py

from kafka import KafkaConsumer
import time
import json
import datetime
from models import *

class MessageConsumer:
    broker = &quot;&quot;
    topic = &quot;&quot;
    group_id = &quot;&quot;
    logger = None

    def __init__(self, broker, topic, group_id):
        self.broker = broker
        self.topic = topic
        self.group_id = group_id

    def activate_listener(self):
        # 처리 시간 등의 결과 확인을 위해 kafka_output.txt 파일에 값을 이어 출력한다.
        sys.stdout = open(&#39;kafka_output.txt&#39;,&#39;a&#39;, encoding=&#39;utf-8&#39;)
        consumer = KafkaConsumer(
            bootstrap_servers=self.broker,
            group_id=self.group_id,
            consumer_timeout_ms=2000,
            auto_offset_reset=&#39;latest&#39;,
            enable_auto_commit=False,
            value_deserializer=lambda m: json.loads(m.decode(&#39;ascii&#39;))
        )
        consumer.subscribe(self.topic)
        tot_start = time.time()
        start = time.time()
        i = 0

        try:
            with open(self.topic+&#39;.txt&#39;, &#39;a&#39;, encoding=&#39;utf-8&#39;) as file:
                for message in consumer:
                    message = message.value
                    file.write(str(message))
                    i = i + 1

                    data = {
                        &#39;name&#39;: topic,
                        &#39;col_a&#39;: i,
                        &#39;col_b&#39;: message,
                        &#39;create_date&#39;: datetime.datetime.now()
                    }
                    TblKafkaTest.objects.create(**data)

                    consumer.commit()

                tot_end = time.time()
                tot_elapsed = tot_end - tot_start
                per_time_value = i / tot_elapsed

                print(&quot;=====================================================&quot;)
                print(&quot;총 처리 시간: &quot;, tot_elapsed)
                print(&quot;총 처리 건수: &quot;, i)
                print(&quot;초당 처리 건수: &quot;, per_time_value)
        except KeyboardInterrupt:
            print(&quot;Aborted by user...&quot;)


broker = &#39;localhost:9092&#39;
topic = &#39;new-topic&#39;
group_id = &#39;consumer-1&#39;

consumer1 = MessageConsumer(broker, topic, group_id)
consumer1.activate_listener()

consumer2 = MessageConsumer(broker, topic, group_id)
consumer2.activate_listener()</code></pre>
<br>

<h3 id="4-실행-및-결과-확인">4. 실행 및 결과 확인</h3>
<p>파이썬 인터프리터에서 producer.py 파일을 실행하였다.
전송 데이터 확인을 위해 print문을 넣었고, 터미널에 다음과 같이 출력되었다.
new-topic.txt 파일에 1~10,000까지의 값이 순차적으로 모두 전송되었음을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/so-eun/post/c53244f8-7d0c-41f3-9676-ef9746c02ee9/image.png" alt=""></p>
<p>데이터 전송은 완료되었으니, <strong>컨슈머에서 확인</strong>을 해보자.
이번에는 consumer.py 를 실행하였고, kafka_output.txt 에서 출력 결과를 확인하였다.</p>
<pre><code class="language-html"># test/kafka_output.txt
=====================================================
총 처리 시간:  10.073350191116333
총 처리 건수:  20003
초당 처리 건수:  1985.7345987674096
=====================================================
총 처리 시간:  9.726698160171509
총 처리 건수:  0
초당 처리 건수:  0.0

</code></pre>
<blockquote>
<p>✍ 나는 <span style="color:purple">컨슈머를 2개로 설정</span>해서 2번의 출력 결과를 얻었다. 
그리고 <span style="color:purple"><strong>consumer1</strong> 이 문제 없이 데이터를 모두 수집</span>하였기 때문에, <strong>consumer2</strong>가 이어 받아 할 작업은 따로 없어 <strong>consumer2</strong>의 <strong>처리 건수는 0</strong>이 된다.</p>
</blockquote>
<p>결과를 확인해보니 총 약 20,000 건의 데이터가 10초동안 처리되었다.
평균적으로 초당 1,985개의 데이터가 처리된 것이다. 엄청난 속도인 것 같다. 😱 </p>
<p><strong>테이블에 insert</strong>도 잘 되었을까? 🤔
<img src="https://velog.velcdn.com/images/so-eun/post/08adf6f9-f6ba-4f68-8d94-24c484d53d9d/image.png" alt="">역시 20,003개의 row 모두 잘 저장되었음을 확인할 수 있다.</p>
<h3 id="추가적으로">추가적으로</h3>
<p>나는 컨슈머 그룹을 설정하고 동일한 그룹ID로 consumer1 , consumer2를 생성하였다.
이렇게 하면 consumer1이 작업을 진행하다가 중단되어도 consumer2가 이어 받아 작업할 수 있게 된다.
단, 여기서 컨슈머 옵션은 <strong>auto_offset_reset=&#39;latest&#39;</strong> 이어야 한다. 
기타 컨슈머의 옵션은 잘 정리된 링크가 있어 첨부한다.
<a href="https://www.devtokki.com/data-platform/apache-kafka/kafka-basic-and-structure/kafka-consumer-overview/">컨슈머 옵션 관련 링크</a></p>
<hr>
<p>데이터 가져와 테이블에 저장하기 실습으로 간단하게 확인해보았지만, 실제로 수만 개의 데이터 처리를 할 때에는 매우 효과적이고 빠를 것 같다는 생각이 들었다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[네트워크 통신 - Stateful과 Stateless]]></title>
            <link>https://velog.io/@so-eun/Stateful%EA%B3%BC-Stateless</link>
            <guid>https://velog.io/@so-eun/Stateful%EA%B3%BC-Stateless</guid>
            <pubDate>Wed, 25 Jan 2023 07:22:47 GMT</pubDate>
            <description><![CDATA[<p>웹 개발을 하다보면, <strong>클라이언트(Client)와 서버(Server)간의 통신</strong>이 필수적이다.
이를 어떻게 관리할 것인지에 대한 방법을 정리해보았다.
상태 유지(Stateful)와 상태 유지하지 않음(Stateless)으로 구분할 수 있는데, 여기서의 &quot;상태 유지&quot;는 말 그대로 서버가 클라이언트의 상태를 보존함을 의미한다.
➡ 즉, <span style="text-decoration:underline">네트워크 프로토콜</span>이다.</p>
<h3 id="stateful상태-유지">stateful(상태 유지)</h3>
<p><span style="color:orange"><strong>통신에 필요한 데이터를 서버가</strong></span> 가지고 있으며, 세션 종료시까지 클라이언트의 상태 정보를 저장하는 것을 의미한다.
작업중이던 서버 중단 시, 중단된 시점부터 다시 진행이 가능하다. 
하지만, 서버 1과 통신 중에 해당 서버에 장애가 생겨 서버 2가 이어 받을 경우, 서버 2는 상태 정보를 가지고 있지 않기 때문에 통신에 문제가 된다. (재인증 절차가 필요하다.)
또한, 서버 확장(scale out) 작업 시에 기존의 데이터와 세션을 모두 옮겨주는 등의 관리가 필요할 수 있다.</p>
<h3 id="stateless무상태">stateless(무상태)</h3>
<p>서버가 클라이언트의 상태 정보를 저장하지 않는다. (요청에 대한 응답만 처리)
<span style="color:orange"><strong>통신에 필요한 데이터는 클라이언트가</strong></span> 가지고 있다가 서버와 통신 시 데이터를 전송하는 방식이다. 서버는 별도의 상태를 저장하지 않고, 해당 요청에 단순 응답만 한다.
그렇기 때문에 서버 1에 장애가 생기더라도, 서버 2가 이어 받아 통신을 진행하는 데에 문제가 없다. 상태 정보는 클라이언트가 가지고 있기 때문이다.
<strong>서버 확장 시에도 수월하게 대처 가능</strong>하다는 장점이 있으며, 클라이언트 요청에 많은 데이터가 소모될 수 있다는 점도 고려해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] 스케일 업(Scale-Up)과 스케일 아웃(Scale-Out)]]></title>
            <link>https://velog.io/@so-eun/AWS-%EC%8A%A4%EC%BC%80%EC%9D%BC-%EC%97%85Scale-Up%EA%B3%BC-%EC%8A%A4%EC%BC%80%EC%9D%BC-%EC%95%84%EC%9B%83Scale-Out</link>
            <guid>https://velog.io/@so-eun/AWS-%EC%8A%A4%EC%BC%80%EC%9D%BC-%EC%97%85Scale-Up%EA%B3%BC-%EC%8A%A4%EC%BC%80%EC%9D%BC-%EC%95%84%EC%9B%83Scale-Out</guid>
            <pubDate>Thu, 19 Jan 2023 04:52:41 GMT</pubDate>
            <description><![CDATA[<p>스케일 업과 스케일 아웃은 <strong>인프라*</strong> 확장을 위한 방법 중 하나이다.</p>
<blockquote>
<p>📌<em>인프라(Infra)</em>* 란?
IT 환경을 운영하고 관리하는 데 필요한 소프트웨어, 하드웨어, 서비스 및 IT 자원의 조합을 의미한다.</p>
</blockquote>
<p>서버를 운영, 관리하다 보면 이용자 수의 증가, 부하 증가 등의 이유로 고성능의 서버가 필요하게 된다.
이러한 문제를 다음 2가지 방안으로 해결할 수 있다.
<img src="https://velog.velcdn.com/images/so-eun/post/3d8c603b-f44b-4c5c-b7b7-ff097b80ebc9/image.png" alt=""></p>
<h3 id="스케일-업scale-up">스케일 업(Scale-Up)</h3>
<p>스케일 업은 말 그대로 기존 서버의 사양을 향상시키는 것이다.
기존 서버에 CPU나 RAM 등을 추가하거나 고성능의 부품, 서버로 교환하는 방식이다. &quot;수직 확장&quot;이라고도 한다.
성능 확장에는 한계가 있고, 성능 확장에 따른 비용 증가 폭이 큰 편이다. 또한, 한 대의 서버에 모든 부하가 집중되므로, 서버에 장애 발생시 영향도가 크게 작용한다.
하나의 서버에서 모든 데이터가 관리되는 <span style="color:purple;font-weight:bold;">데이터베이스 서버에 적합</span>한 방식이다.</p>
<h3 id="스케일-아웃scale-out">스케일 아웃(Scale-Out)</h3>
<p>스케일 아웃은 서버 대수를 늘려 성능을 확장하는 방식이다.
&quot;수평 확장&quot; 이라고도 하며, 서버의 부하를 여러 대가 균등하게 나누어 갖게 된다. 또한, 서버 한 대가 장애로 다운되더라도 다른 서버로 서비스 제공이 가능하다.
서버 대수 증가 비용 부담은 비교적 적으며, <strong>분산 처리에 적합</strong>하다.
모든 서버가 동일한 데이터를 갖고 있어야 하므로, 데이터의 변화가 빈번하지 않은 <span style="color:purple;font-weight:bold;">웹 서버에 적합</span>하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 관리 - Agile과 Waterfall]]></title>
            <link>https://velog.io/@so-eun/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B4%80%EB%A6%AC-Agile-%EA%B3%BC-Waterfall</link>
            <guid>https://velog.io/@so-eun/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B4%80%EB%A6%AC-Agile-%EA%B3%BC-Waterfall</guid>
            <pubDate>Mon, 26 Dec 2022 01:44:43 GMT</pubDate>
            <description><![CDATA[<p>** 애자일 vs 워터폴 **
프로젝트를 진행할 때 여러가지 방법론들이 있다. 
그 중에서 대표적으로 많이 사용되는 애자일 방법론과 워터폴 방법론에 대해 알아본다.</p>
<p><img src="https://velog.velcdn.com/images/so-eun/post/fa93ac57-cb6b-481c-ab41-257dbc8bb84f/image.png" alt=""> <div style="text-align:center;"><a href="https://blog.jandi.com/ko/2022/02/11/strategic_collaboration/">이미지 출처:잔디 블로그</a></div></p>
<h2 id="span-stylecolorb22222-애자일-agile-반복-접근-방식-span"><span style="color:#B22222"> 애자일 (Agile) &quot;반복 접근 방식&quot; </span></h2>
<p>짧고 점진적인 주기로 개발하는 방법론이며, 제품이나 서비스 개발을 지속적으로 향상시킨다.
일정한 주기를 바탕으로 끊임없이 프로토타입을 만들어 고객의 요구사항을 만족시키는 방식이다.</p>
<blockquote>
<p><strong>(요구사항 분석 -&gt; 설계 -&gt; 개발 -&gt; 테스트) * N -&gt; 배포</strong>
작은 기능 단위로 짧은 주기로 빠르게 단위 테스트를 거친 후 보완하는 방식을 여러 번 걸친다.</p>
</blockquote>
<ul>
<li>각각의 한 주기 = <span style="color:green"><strong>스프린트(Sprint)</strong></span></li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>고객 중심적이며, 환경 변화에 잘 적응한다.</li>
<li>진행 속도가 빠르며, 결과물의 만족도가 높아진다.</li>
<li>잛고 반복적인 스프린트 과정을 거치기 때문에 개발 과정 중에 서비스 변경이 자유롭다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>강력한 팀워크와 팀원 모두에게 책임감이 요구된다.</li>
<li>빠른 반복작업에 익숙한 스크럼 마스터가 필요하다.</li>
</ul>
<br/>

<h2 id="span-stylecolor008b8b-워터폴-waterfall-순차-접근-방식-span"><span style="color:#008B8B"> 워터폴 (Waterfall) &quot;순차 접근 방식&quot; </span></h2>
<p>대부분의 회사에서 많이 사용하는 방법론으로, 프로젝트 시작부터 마감까지 일련의 순서에 따라 이뤄지는 방식이다. 
프로세스 주기나 순서가 거의 일정하다. 업무 프로세스가 잘 정착된 환경에서 업무를 능숙하게 처리한다.</p>
<blockquote>
<p><strong>요구사항 분석 -&gt; 설계 -&gt; 개발 -&gt; 테스트 -&gt; 배포</strong>
전체 프로젝트를 하나의 과정으로 순차적으로 진행된다.</p>
</blockquote>
<h3 id="장점-1">장점</h3>
<ul>
<li>프로세스가 길고 순서가 정해져있어 팀 규모와 상관없이 안정적으로 진행할 수 있다.</li>
<li>초기 단계에 요구사항과 예산이 정해져있어, 기술적인 리스크를 최소화한다.</li>
</ul>
<h3 id="단점-1">단점</h3>
<ul>
<li>예측하지 못한 리스크에 많은 비용이 발생한다.</li>
<li>요구사항이 초기에 정해져 변경에 자유롭지 못하다.</li>
<li>순차적인 진행 방식에 따라, 이전의 과정이 완료되어야 다음 과정으로 진행이 가능해 개발 속도가 느리다.</li>
</ul>
<blockquote>
<h3 id="📌-결론">📌 결론</h3>
<p>두 유형 모두 각각의 장단점이 있으며, 방법론에 있어 정답은 없다.
프로젝트의 목표 및 일정 등에 따라 각 조직에 따라 프로젝트 개발에 적합한 방법론을 선택해 진행하면 된다.</p>
</blockquote>
<br/>

<h3 id="마치며">마치며..</h3>
<p>나는 지금까지 어떠한 방법론을 정하고 작업하진 않았지만, 두 방법론을 배워가면서 정리해보니 워터폴의 방식으로 프로젝트를 진행해왔구나 라는 생각이 든다.
아마도 대부분의 일반적인 기업에서는 워터폴의 방식으로 진행하지 않을까 싶다.
<span style="color:darkgreen;font-weight:bold">여러 스프린트를 거쳐 고객의 요구사항을 만족시킬 수 있는 애자일</span>의 방식을 도입하는 것, 어렵지만 충분히 고려해볼 문제인 것 같다.😎</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kafka] 윈도우 로컬 에서 카프카 실행해보기]]></title>
            <link>https://velog.io/@so-eun/Kafka-%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%A1%9C%EC%BB%AC-%EC%97%90%EC%84%9C-%EC%B9%B4%ED%94%84%EC%B9%B4-%EC%8B%A4%ED%96%89%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@so-eun/Kafka-%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%A1%9C%EC%BB%AC-%EC%97%90%EC%84%9C-%EC%B9%B4%ED%94%84%EC%B9%B4-%EC%8B%A4%ED%96%89%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 11 Dec 2022 10:36:13 GMT</pubDate>
            <description><![CDATA[<p>이전 두개의 포스팅에서 카프카의 개념 및 주요 내용에 대해 다루었다.
이제 윈도우 환경에서 카프카를 실제로 테스트 해보도록 한다.</p>
<p>카프카의 기본 구성 요소 등을 모두 학습했다는 가정하에 작성하였다.</p>
<hr>
<blockquote>
<p>실행 환경: Window
기본 설치 요소: Java, Zookeeper, Kafka
kafka 설치 폴더 경로: C:\kafka\kafka_2.13-3.3.1</p>
</blockquote>
<h3 id="📌-실행에-필요한-기본-환경-구축">📌 실행에 필요한 기본 환경 구축</h3>
<h4 id="👉-java-설치"><strong>👉 Java 설치</strong></h4>
<p>주키퍼는 독립적으로 사용이 불가능하며 자바에 의존성이 있기 때문에, 오라클 자바를 설치해야 한다. 
설치 후 경로를 복사하여 시스템 속성 &gt; 고급 &gt; 환경 변수 &gt; 시스템 변수 항목에 다음을 추가해준다.</p>
<pre><code> JAVA_HOME = C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.332-1 (본인의 자바 설치 경로)</code></pre><p><img src="https://velog.velcdn.com/images/so-eun/post/01a413b6-0f8d-45fd-a3cd-9b7a19b5ae1c/image.png" alt=""></p>
<h4 id="👉-zookeeper-설치"><strong>👉 Zookeeper 설치</strong></h4>
<ul>
<li><a href="https://archive.apache.org/dist/zookeeper/stable/">https://archive.apache.org/dist/zookeeper/stable/</a></li>
<li>카프카를 띄우기 위해 반드시 실행해야 하는 주키퍼를 다운로드한다.
<img src="https://velog.velcdn.com/images/so-eun/post/480d8f4e-61cb-4f9e-847e-4d9143a2cac5/image.png" alt=""></li>
</ul>
<h4 id="👉-kafka-설치"><strong>👉 Kafka 설치</strong></h4>
<ul>
<li><a href="https://kafka.apache.org">https://kafka.apache.org</a></li>
<li>위의 경로에서 tgz 파일을 다운받은 후 적절한 경로에 압축을 해제한다.
<img src="https://velog.velcdn.com/images/so-eun/post/ae75198f-180c-4d6d-b593-2f4685e66e8e/image.png" alt=""></li>
</ul>
<hr>
<h3 id="📌-실행해보기">📌 실행해보기</h3>
<h4 id="1-zookeeper-서버-띄우기"><strong>1. Zookeeper 서버 띄우기</strong></h4>
<ul>
<li>윈도우 cmd 창을 열고 주키퍼를 띄운다.</li>
<li>kafka 설치 경로에서 실행한다. (예제의 경우, <em><strong>C:\kafka\kafka_2.13-3.3.1</strong></em>)<pre><code>C:\kafka\kafka_2.13-3.3.1&gt; .\bin\windows\zookeeper-server-start.bat config\zookeeper.properties</code></pre><img src="https://velog.velcdn.com/images/so-eun/post/81c34e66-39bf-4fd5-8dc7-d6d1ade7406b/image.png" alt=""></li>
</ul>
<p>그러면 위와 같이 이런 저런 메시지들이 나오고, 주키퍼가 정상 실행 되었음을 알리는 zookeeper 문구를 확인할 수 있다.<img src="https://velog.velcdn.com/images/so-eun/post/5198ae23-ef5d-4a0a-910c-3fcd33dd9c00/image.png" alt="">그리고 하단으로 쭉~스크롤하면 커서가 깜빡깜빡 하고 있을 것이다. 여기까지 왔다면 주키퍼 띄우기 성공이다. </p>
<h4 id="2-kafka-서버-띄우기"><strong>2. Kafka 서버 띄우기</strong></h4>
<ul>
<li>위에서 띄운 cmd 창은 그대로 두고, <span style="color:green">다른 cmd 창을 열어</span> 이번에는 kafka를 띄워본다.</li>
<li>역시 이전과 <strong>동일한 경로</strong>에서 진행한다.<pre><code>C:\kafka\kafka_2.13-3.3.1&gt; .\bin\windows\kafka-server-start.bat config\server.properties</code></pre><img src="https://velog.velcdn.com/images/so-eun/post/c7da7085-c9a0-44de-bc72-fbd070d4449d/image.png" alt=""></li>
</ul>
<p>❌ <strong><span style="color:darkorange">그런데 나는 이 과정에서 에러를 마주했다.</span></strong> ❌<img src="https://velog.velcdn.com/images/so-eun/post/5ff47511-f320-45dd-a21f-9131b886b40c/image.png" alt="">1에서 실행한 주키퍼 서버에서 &quot;java.io.IOException: 현재 연결은 원격 호스트에 의해 강제로 끊겼습니다&quot; 와 같은 에러 메시지가 나타났고, 카프카 서버 실행을 시도했던 cmd 창에서도 다음과 같은 메시지와 함께 이 과정에 실패하였다.
<img src="https://velog.velcdn.com/images/so-eun/post/3447639c-2a7d-478f-b9f9-a8ed32660a7d/image.png" alt=""></p>
<p>구글링 후, 다음과 같은 방법으로 해결하였다.</p>
<pre><code>..
log.dirs=/tmp/kafka-logs
..</code></pre><p><strong>카프카 설치 디렉토리/config/server.properties</strong> 파일의 log.dirs 항목에서 설정되어 있는 카프카 log path로 가보면 meta.properties라는 파일이 있다. 해당 파일을 지워주고 카프카를 재시작하였다. </p>
<span style="background-color:#FFE4C4">
실행 과정 중 비슷한 오류가 몇 번 있었다.<br>
  zookeeper, kafka를 실행시키면 기본적으로 <span style="font-weight:bold">C:\tmp 위치에 로그 정보가 생성</span>된다.
이전에 설치되었던 정보가 있거나 실행 시 오류가 발생할 경우 <span style="font-weight:bold">tmp 폴더를 제거</span>하고 zookeeper와 kafka를 다시 실행시키면 대부분 정상 동작한다.<span>

<p><img src="https://velog.velcdn.com/images/so-eun/post/6d45646a-6d7b-4422-a54a-43aee878a32b/image.png" alt=""><img src="https://velog.velcdn.com/images/so-eun/post/d4286277-d4b1-43b1-8519-617c3a2eb380/image.png" alt="">역시, 1의 결과와 동일하게 메시지가 쭈욱 나온다. 실행 성공!🙆‍♀️</p>
<h4 id="3-kafka-토픽-생성하기"><strong>3. Kafka 토픽 생성하기</strong></h4>
<p>이제 실행에 필요한 환경은 구축하였다. 실제로 토픽을 생성해보자.</p>
<ul>
<li>이번에도 <span style="color:green">새로운 cmd 창을 열어</span> 진행한다.<pre><code>C:\kafka\kafka_2.13-3.3.1\bin\windows&gt; .\kafka-topics.bat --create --topic [topic name] --bootstrap-server [host]:[port]  --partition 1</code></pre></li>
<li>옵션:
  --create --topic: 토픽을 생성한다.
  --bootstrap-server: 클라이언트가 접근하는 토픽의 메타데이터를 요청하여 원하는 브로커를 찾기 위한 설정이다.<pre><code>--_replication factor_ n: 토픽의 파티션 복제본 개수를 설정한다.</code></pre>  --partition n: 파티션의 개수를 설정한다.</li>
</ul>
<blockquote>
<h3 id="🤚-여기서-replication-factor란">🤚 여기서, replication factor란?</h3>
<p>토픽 파티션의 복제본을 몇 개를 생성할 지에 대한 설정이다.
 eg) replication factor:3 -&gt; 복제본을 2개 생성한다. 
<img src="https://velog.velcdn.com/images/so-eun/post/4f21b997-7a40-4ae6-badd-59516ee0cb65/image.png" alt=""><div style="text-align:center;"><a href="https://www.popit.kr/kafka%EC%9A%B4%EC%98%81%EC%9E%90%EA%B0%80-%EB%A7%90%ED%95%98%EB%8A%94-topic-replication/">이미지 출처</a></div> 
메시지를 <strong>복제</strong>해 관리하면서, 장애 발생시 신속하게 작업을 이어 받아 다른 브로커가 역할을 대신할 수 있도록 도와줄 수 있다. 중요한 데이터의 경우 replication factor를 크게 설정하는 것이 데이터 처리에 효과적일 것이다. 
  하지만, replication factor가 <strong>많다고 무조건 좋은 것은 아니다</strong>. 데이터 복제로 인한 성능이 저하될 수 있는 점은 고려해 설정하는 것이 좋다. 
  여기서의 <strong>ISR(In-Sync Replication)</strong>은 replication factor의 group이라고 볼 수 있다. 각각의 동일한 replication factor로 묶인 그룹을 의미한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/so-eun/post/46ba895d-c1a8-4080-aec2-5ba25e30d41c/image.png" alt="">토픽 생성 결과를 확인해보자. 
  매우 단순하다. 내가 지정한 토픽명(new-topic)이 생성되었다.🙂</p>
<pre><code>Created topic new-topic.</code></pre><h4 id="4-producer로-topic에-메시지-전달하기"><strong>4. Producer로 Topic에 메시지 전달하기</strong></h4>
<p>이제 메인 기능, 위에서 생성한 토픽에 실제 메시지를 전달해보자.
  <em>(토픽 명: new-topic/ localhost:9092)</em></p>
<pre><code>C:\kafka\kafka_2.13-3.3.1\bin\windows&gt; .\kafka-console-producer.bat --broker-list localhost:9092 --topic new-topic</code></pre><p>  다음과 같이 new-topic에 여러 개의 메시지를 전송하였다.
  <img src="https://velog.velcdn.com/images/so-eun/post/761e14fe-7fd9-4ea1-98e5-8cf2d454226d/image.png" alt=""></p>
<h4 id="5-생성한-topic-consumer로-구독해-데이터-받아오기"><strong>5. 생성한 Topic Consumer로 구독해 데이터 받아오기</strong></h4>
<p>이제 Producer가 발행한 메시지를 Consumer가 받아올 차례이다.</p>
<ul>
<li><span style="color:green">새로운 cmd 창을 열어</span> 앞에서 생성한 토픽을 구독해보자.</li>
</ul>
<pre><code>C:\kafka\kafka_2.13-3.3.1\bin\windows&gt; .\kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic new-topic --from-beginning</code></pre><ul>
<li>옵션:</li>
<li>-from-beginning: 이전의 데이터를 모두 출력한다. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/so-eun/post/98b656d4-306e-4fae-959c-644960c70ee5/image.png" alt="">프로듀서가 보낸 데이터 그대로 실시간으로 컨슈머가 수신하는 것을 확인할 수 있다.</p>
<hr>
<h3 id="마치며">마치며,</h3>
<p>  지금까지 윈도우 로컬 환경에서 가벼운 예제로 카프카- 프로듀서와 컨슈머를 실습해보았다. 상용 서버에 적용할 때에는 카프카 전용 서버도 따로 두는 등 기본적인 환경부터 다르겠지만, 개념을 익히며 가볍게 이해해 본 정도로 오늘은 여기에서 마무리한다.</p>
<p>이후에는 한 단계 더 나아가서 파이썬에서 많은 양의 데이터를 주고 받아 테이블에 insert하는 예제도 다뤄보아야겠다. 🙋</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kafka] 주요 개념 Offset, Consumer Group, Rebalancing]]></title>
            <link>https://velog.io/@so-eun/Kafka-Offset-Consumer-Group-Rebalancing</link>
            <guid>https://velog.io/@so-eun/Kafka-Offset-Consumer-Group-Rebalancing</guid>
            <pubDate>Fri, 09 Dec 2022 05:36:09 GMT</pubDate>
            <description><![CDATA[<p>지난 내용에 이어, 오늘은 카프카의 오프셋, 리밸런싱, 컨슈머 그룹에 대해 정리해본다. 먼저, 각 개념에 대해 알아보자.</p>
<h3 id="offset-오프셋">Offset (오프셋)</h3>
<p>오프셋이란, <strong>각 파티션마다 메시지가 저장되는 위치</strong>를 의미한다. 
당연히 오프셋 값은 &#39;<strong>파티션 내에서</strong>&#39; 고유하고, 순차적으로 표기된다. 
테이블의 pk (id) 개념과 비슷하다고 볼 수 있다.</p>
<p>컨슈머가 메세지를 어디까지 읽었는지 저장하고 있기 때문에 다음에 읽을 메시지 정보를 쉽게 알 수 있다.</p>
<p>예를 들어, 현재 offset = 3이라면, 컨슈머가 0~2까지 메시지를 읽은 것이고 다음에 읽어야 하는 메세지 정보는 3번 이라는 것을 의미한다.
이러한 offset을 통해 <span style="color:brown">데이터의 순서가 보장</span>되는 것이다.</p>
<h3 id="consumer-group-컨슈머-그룹">Consumer Group (컨슈머 그룹)</h3>
<p>말 그대로 컨슈머들의 집합, <strong>여러 컨슈머들이 하나로 묶인 논리적인 그룹</strong>이다. 한 컨슈머 그룹 내에서 1번 컨슈머에서 장애가 발생할 경우, 같은 그룹 내의 2번 컨슈머가 이어 받아 데이터를 계속해서 받아올 수 있다.</p>
<h3 id="rebalancing-리밸런싱">Rebalancing (리밸런싱)</h3>
<p><img src="https://velog.velcdn.com/images/so-eun/post/819b0517-1f47-4075-b2e2-eb24d3d41eb1/image.png" alt=""></p>
<p>리밸런싱 관련 검색을 하던 도중 이해하기 쉬운 이미지가 있어 가져왔다. (<a href="https://ksr930.tistory.com/248">원본 링크</a>)
리밸런싱이란, 위의 컨슈머 그룹에서 설명한 예와 같이 <strong>한 컨슈머로부터 다른 컨슈머가 소유관을 이관받는 작업</strong>을 의미한다.</p>
<p>컨슈머 그룹 내의 컨슈머들은 자신들의 파티션 소유권을 서로 공유한다. 
<strong><span style="color:brown">이러한 리밸런싱은 컨슈머 그룹의 가용성과 확장성을 높여준다.</span></strong></p>
<p>리밸런싱이 발생하면 컨슈머들은 메시지를 읽을 수 없어 해당 컨슈머 그룹 전체가 사용 불가 상태가 되어버린다. 리밸런싱이 발생하지 않는 것이 가장 좋지만,</p>
<h3 id="❓-그렇다면-리밸런싱은-언제-발생할까">❓ 그렇다면, 리밸런싱은 언제 발생할까?</h3>
<p>컨슈머 그룹이 생성되면 컨슈머 그룹별로 관리하는 브로커가 지정되는데, 백그라운드 프로세스로 실행되는 이 브로커를 <strong>컨슈머 그룹 코디네이터(Consumer Group Coordinator)</strong> 라고 한다.
이 코디네이터가 컨슈머 그룹을 관리하면서 다음과 같은 상황이 발생되면 리밸런싱이 실행된다.</p>
<p><span style="color:darkgreen">1) session.timeout.ms 설정시간에 heartbeat 시그널을 받지 못하는 경우</span></p>
<p><span style="color:darkgreen">2) max.poll.interval.ms 설정시간에 poll() 메소드가 호출되지 않는 경우</span></p>
<ul>
<li>컨슈머는 메시지를 가져오기 위해 브로커에 <strong>poll()</strong>요청을 보내고, 컨슈머는 가져온 메시지를 처리한 후 해당 파티션의 offset을 커밋한다.
<img src="https://velog.velcdn.com/images/so-eun/post/e0f6e5e5-1376-46ae-a034-148bafa60833/image.png" alt=""><a href="https://saramin.github.io/2019-09-17-kafka/">이미지 출처:사람인 기술 블로그</a>
poll()요청을 보낸 후 다음 요청을 다시 보내기까지의 시간이 [<strong>max.poll.interval.ms</strong>]의 기본값인 <strong>300000(5분)</strong> 보다 늦으면 브로커는 컨슈머에 문제가 있다고 판단하여 리밸런싱을 발생시킨다.</li>
</ul>
<table style="border:none;background-color:none;">
  <thead>
    <tr style="border-bottom:1px solid lightgray;">
      <th style="width:20%">옵션명</th>
      <th>설명</th>
      <th style="width:10%">기본값</th>
    </tr>
  </thead>
  <tbody>
    <tr style="border-bottom:1px solid lightgray;">
      <td><span style="color:purple;font-weight:bold">session.timeout.ms</span></td>
      <td>컨슈머와 브로커 사이의 session timeout 시간.<br>컨슈머가 살아있는 것으로 판단하는 시간으로 <strong>이 시간이 지나면 해당 컨슈머는 종료되거나 장애가 발생한 것으로 판단하고 컨슈머 그룹은 리밸런스를 시도한다.</strong><br> 이 옵션은 heartbeat 없이 얼마나 오랫동안 컨슈머가 있을 수 있는지를 제어하며 heartbeat.interval.ms와 밀접한 관련이 있어서 일반적으로 두 속성이 함께 수정된다.</td>
      <td>10000 (10초)</td>
    </tr>
    <tr style="border-bottom:1px solid lightgray;">
      <td><span style="color:purple;font-weight:bold">heartbeat.interval.ms</span></td>
      <td>컨슈머가 얼마나 자주 heartbeat를 보낼지 조정한다. session.timeout.ms보다 작아야 하며 일반적으로 1/3로 설정한다.</td>
      <td>3000 (3초)</td>
    </tr>
    <tr style="border-bottom:1px solid lightgray;">
      <td><span style="color:purple;font-weight:bold">max.poll.interval.ms</span></td>
      <td>컨슈머가 polling하고 commit 할 때 까지의 대기시간. 컨슈머가 살아 있는지를 체크하기 위해 hearbeat를 주기적으로 보내는데, 계속해서 heartbeat만 보내고 실제로 메시지를 가져가지 않는 경우가 있을 수 있다.<br>이러한 경우에 컨슈머가 무한정 해당 파티션을 점유할 수 없도록 <strong>주기적으로 poll을 호출하지 않으면 장애라고 판단하고 컨슈머 그룹에서 제외</strong>시키도록 하는 옵션이다.</td>
      <td>300000 (5분)</td>
    </tr>
    <tr style="border-bottom:1px solid lightgray;">
      <td><span style="color:purple;font-weight:bold">max.poll.records</span></td>
      <td>컨슈머가 최대로 가져갈 수 있는 개수이다.<br>이 옵션으로 polling loop에서 데이터 양을 조정할 수 있다.</td>
      <td>500</td>
    </tr>
    <tr style="border-bottom:1px solid lightgray;">
      <td><span style="color:purple;font-weight:bold">enable.auto.commit</span></td>
      <td>백그라운드에서 주기적으로 offset을 commit</td>
      <td>true</td>
    </tr>
    <tr style="border-bottom:1px solid lightgray;">
      <td><span style="color:purple;font-weight:bold">auto.commit.interval.ms</span></td>
      <td>주기적으로 offset을 커밋하는 시간</td>
      <td>5000 (5초)</td>
    </tr>
    <tr style="border-bottom:1px solid lightgray;">
      <td><span style="color:purple;font-weight:bold">auto.offset.reset</span></td>
      <td>earliest: 가장 초기의 offset 값으로 설정<br>latest: 가장 마지막의 offset 값으로 설정<br>none: 이전 offset 값을 찾지 못하면 error 발생</td>
      <td>latest</td>
    </tr>
  </tbody>
</table>]]></description>
        </item>
        <item>
            <title><![CDATA[[Kafka] 대용량, 실시간 데이터 처리에 적합한 카프카]]></title>
            <link>https://velog.io/@so-eun/Kafka-%EB%8C%80%EC%9A%A9%EB%9F%89-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC%EC%97%90-%EC%A0%81%ED%95%A9%ED%95%9C-%EC%B9%B4%ED%94%84%EC%B9%B4</link>
            <guid>https://velog.io/@so-eun/Kafka-%EB%8C%80%EC%9A%A9%EB%9F%89-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC%EC%97%90-%EC%A0%81%ED%95%A9%ED%95%9C-%EC%B9%B4%ED%94%84%EC%B9%B4</guid>
            <pubDate>Sun, 04 Dec 2022 13:13:26 GMT</pubDate>
            <description><![CDATA[<h3 id="배경">배경</h3>
<p>이번에 대용량 데이터를 실시간으로 처리하는 업무를 맡았다.
개발 과정에서는 특별한 무리는 없었지만, 실제 서비스가 될 경우 유저가 많아질수록 트래픽이 많이 발생하는 현상이 우려되었다.
이를 효과적으로 처리하기 위해 도입한 것이 바로 카프카(Kafka)이다.</p>
<h2 id="kafka란-무엇일까">&quot;Kafka&quot;란 무엇일까?</h2>
<p>2011년 미국 링크드인(Linkedin)에서 개발한 것이다.
데이터 파이프라인, 분산 스트리밍, 실시간 스트리밍 데이터를 효과적으로 처리하기 위한 <strong>고성능 분산형 게시-구독 메시지 플랫폼</strong>이다.</p>
<p>카프카(Kafka)는 <span style="color:green"><strong>Pub-Sub 모델의 메시지 큐 형태로 동작</strong></span>하며 분산 환경에 특화되어 있다.</p>
<blockquote>
<p>   <strong>Pub-Sub 모델</strong>이란?
일반적으로 메시징 서비스 모델은 Queue 방식과 Pub-Sub 구조로 구분되는데, 카프카는 후자에 해당한다. 
Queue는 송신자와 수신자가 1:1 관계이다. 단일 송신자가 단일 수신자에게 데이터를 전송한다.
Pub-Sub 구조는 송신자와 수신자가 m:n 관계이다. 즉, 여러 송신자가 메시지를 발행하고, 여러 수신자가 메시지를 구독한다. </p>
</blockquote>
<p><em>한 마디로, 실시간으로 많은 데이터를 처리해야 할 때 빠르고 효과적인 분산 시스템이라고 할 수 있다.</em></p>
<p>현재 LINE, 링크드인에서 카프카를 도입해 사용하고 있으며, 국내에서도 사용량이 증가하는 추세이다.</p>
<h2 id="kafka의-구성-요소">kafka의 구성 요소</h2>
<p>대략 어떤 시스템인지는 이해가 되었으니, 구성 요소를 먼저 알아보자.
<img src="https://velog.velcdn.com/images/so-eun/post/4481645b-f1d8-4a4e-aac7-a339ae350dd3/image.png" alt="">이미지 출처: <a href="http://www.soutier.de/blog/2018/08/14/fast-data-intro-kafka/">원본 링크</a>
이해하기 쉽게 표현된 이미지가 있어 해당 링크에서 가져왔다.</p>
<ul>
<li><h3 id="producer-프로듀서">Producer (프로듀서)</h3>
<ul>
<li>메시지(이벤트)를 <strong>생성</strong>해 카프카 클러스터에 전송한다.</li>
</ul>
</li>
<li><h3 id="topic-토픽">Topic (토픽)</h3>
<ul>
<li>메시지를 논리적으로 묶은 단위이다. (예를 들어 데이터베이스의 테이블, 파일에서 폴더에 해당)</li>
<li>프로듀서가 메시지를 보낼 경우 저장되는 장소이다.</li>
<li>하나의 토픽은 여러 개의 파티션으로 이루어져 있다. </li>
</ul>
</li>
<li><h3 id="partition-파티션">Partition (파티션)</h3>
<ul>
<li>토픽 내에 분리된 공간이며, 각 토픽 당 데이터를 분산 처리하는 가장 작은 단위이다. </li>
<li>조금 더 쉬운 이해를 위해 예를 들자면 토픽을 톨게이트, 파티션을 n차선이라고 비유한다. </li>
</ul>
</li>
<li><h3 id="broker-브로커">Broker (브로커)</h3>
<ul>
<li>각각의 카프카 서버, <strong>여러 개</strong>의 브로커 생성이 가능하다.</li>
<li>메시지를 <strong>저장</strong>하고 관리한다.</li>
</ul>
</li>
<li><h3 id="consumer-컨슈머">Consumer (컨슈머)</h3>
<ul>
<li>메시지를 <strong>구독</strong>해 데이터를 받아온다.</li>
<li>하나의 컨슈머에서 여러 개의 토픽 데이터를 받아올 경우, 처리하는 데 버거울 수 있다. 이럴 경우 같은 컨슈머 <strong>그룹 내에 또 다른 컨슈머를 추가</strong>해 작업을 각각 수행하면 더욱 효율적인 결과를 낼 수 있다.</li>
</ul>
</li>
<li><h3 id="zookeeper-주키퍼">Zookeeper (주키퍼)</h3>
<ul>
<li>분산 메세지 큐의 메타 데이터 정보를 관리한다. (브로커id, 컨트롤러id 등)</li>
<li>kafka를 띄우기 위해서는 <strong>반드시 실행</strong>되어야 한다.</li>
<li>Zookeeper는 <strong>홀수의 서버</strong>로 작동하도록 설계되어 있다. (최소 3, 권장 5)</li>
</ul>
</li>
</ul>
<h2 id="kafka의-특징">kafka의 특징</h2>
<p><img src="https://velog.velcdn.com/images/so-eun/post/22a26d91-112d-44ce-924f-3c8e9d65dcdc/image.png" alt=""></p>
<ul>
<li><p><span style="color:purple;background-color:lightgray;font-size:20px"><strong>고성능</strong></span> : 프로듀서와 컨슈머가 브로커를 통해 데이터를 주고 받을 때, 한 번에 대용량의 데이터를 전송 가능하다. 또한, 컨슈머의 개수를 늘리면서 병렬 처리까지 가능하니 효율성이 높아진다고 볼 수 있다.</p>
</li>
<li><p><span style="color:purple;background-color:lightgray;font-size:20px"><strong>고가용성 및 확장성</strong></span> : 카프카는 클러스터(*각기 다른 서버들을 하나로 묶어서 하나의 시스템같이 동작하게 함)로 동작 함으로로써 클라이언트들에게 고가용성의 서비스를 제공한다. 또한, 서버를 수평적으로 늘려 안정성과 성능을 향상시키는 <span style="text-decoration:underline">스케일 아웃(Scale-out)</span>이 가능하다.
브로커 서버를 여러 개로 운영하기 때문에, 일부 서버에 문제가 발생해도 나머지 서버에 영향 없어 중단되지 않고 동작 가능하다.</p>
</li>
<li><p><span style="color:purple;background-color:lightgray;font-size:20px"><strong>디스크에 저장</strong></span> : 카프카는 메시지를 순차적으로 디스크에 저장한다. 그렇기 때문에, 서버에서 장애가 나더라도 실제 메시지는 디스크에 존재하기 때문에 <span style="text-decoration:underline">데이터 유실 걱정이 없다.</span></p>
</li>
<li><p><span style="color:purple;background-color:lightgray;font-size:20px"><strong>분산 처리에 특화</strong></span> : 여러 개의 파티션을 서버에 분산시켜 나누어 처리하기 때문에 속도가 향상된다. </p>
</li>
</ul>
<hr>
<h3 id="📌-알아-둘-사항">📌 알아 둘 사항</h3>
<ul>
<li>한 토픽의 파티션 개수보다 더 많은 수의 컨슈머를 추가하는 건 의미가 없다.</li>
<li>파티션이 많다고 무조건 좋은 것은 아니다.</li>
<li><span style="color:orange">하나의 파티션을 두개 이상의 컨슈머가 소비할 수 없다.</span></li>
</ul>
<hr>
<p>이렇게 정리하고 보니, 대용량 및 실시간 처리에 적합한 카프카가 사용될 수 있는 곳이 많을 것 같다. 이런 훌륭한 기능을 가진 시스템을 사용하지 않을 이유가 없다.
실시간으로 전송되는 알림, 접속 로그, 실시간 집계 등의 처리에서 아주 빠르고 효과적으로 개선될 수 있을 것 같다.
카프카의 <a href="https://velog.io/@so-eun/Kafka-Offset-Consumer-Group-Rebalancing">오프셋, 리밸런싱, 다중 컨슈머 그룹</a> 등에 대한 내용은 추가적으로 다루도록 한다.😄</p>
<hr>
<h3 id="추가적으로">추가적으로...</h3>
<p>카프카의 메타데이터 관리 도구인 <strong>주키퍼(Zookeeper)</strong>가 점차 제거된다고 한다.
<a href="https://www.ciokorea.com/news/235594">관련 링크</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] sync_to_async (Feat. ninja) ]]></title>
            <link>https://velog.io/@so-eun/Django-synctoasync-Feat.-ninja</link>
            <guid>https://velog.io/@so-eun/Django-synctoasync-Feat.-ninja</guid>
            <pubDate>Tue, 29 Nov 2022 08:40:37 GMT</pubDate>
            <description><![CDATA[<p>이전 글에서는 wsgi, asgi에 대해 다루었다. 
오늘은 <em>동기 메서드를 코루틴처럼 비동기로</em> 쓸 수 있도록 변환해주는 callable object, 
<strong>asgiref 패키지</strong>의 <strong><span style="color:green">sync_to_async</span></strong>에 대해 알아보도록 한다.</p>
<hr>
<p>🤚 이전 글에서 다뤘던 주요 내용에 대해 다시 정리해보자.
파이썬 어플리케이션과 서버를 연결해주는 &quot;<strong>WSGI</strong>&quot; 동기 함수만을 처리해 여러 작업 동시 수행에는 한계가 있었다. 이르 보완하기 위한 것이 바로 &quot;<strong>ASGI</strong>&quot;였다.
ASGI는 기본적으로 요청을 비동기로 처리한다. 이 ASGI의 대표적인 예가 uvicorn이었다.</p>
<hr>
<h3 id="django-ninja의-async">Django Ninja의 Async</h3>
<p>본론으로 들어가서, Django Ninja의 Async 예제를 살펴보자.
기본적으로 장고의 ORM은 sync(동기적)로 작동한다. 우리는 async로 작동하도록 구현하기 위해 기존 방법과는 다르게 아래와 같이 2가지 방법을 통해 <span style="color:brown;font-weight:bold">Sync(동기) -&gt; Async(비동기)</span> 로 설정 가능하다. </p>
<p>아래 2가지 모두 동일하게 asgiref 에서 제공하는 sync_to_async 모듈을 import 해준다.</p>
<h3 id="☝-span-stylefont-weightbold함수span로-사용">☝ <span style="font-weight:bold">함수</span>로 사용</h3>
<pre><code>from asgiref.sync import sync_to_async 

async_function = sync_to_async(sync_function, thread_sensitive=False)
async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)
</code></pre><p>동기화 함수는 모두 메인 스레드에서 실행된다고 가정하며, 2가지 스레드 모드가 있다.</p>
<p>여기서 <span style="color:orange">thread_sensitive</span> 라는 값이 있는데, 기본 값은 True이다.
True로 설정하면 다른 기능들과 동일한 기존 스레드에서 실행되고, False로 설정하면 완전히 새로운 스레드에서 실행되며 호출이 완료되면 닫힌다.</p>
<hr>
<h3 id="✌-span-stylefont-weightbold데코레이터span로-사용">✌ <span style="font-weight:bold">데코레이터</span>로 사용</h3>
<pre><code>from asgiref.sync import sync_to_async

@sync_to_async
def sync_function(...):
   ...</code></pre><p>이렇게 sync 함수 상단에 데코레이터로 선언해주고 sync 함수 그대로 작성하면 해당 함수가 async로 작동하기 때문에 더욱 간편하게 사용 가능하다.</p>
<hr>
<h3 id="django-ninja에서-사용하기">Django-ninja에서 사용하기</h3>
<p>Django-ninja에서는 router를 이용해 URL을 설정한다.
이 때, sync_to_async 데코레이터와 함께 사용하려면 다음과 같은 순서에 맞춰 적용한다.</p>
<blockquote>
<p>sync_to_async 데코레이터를 먼저, path를 나중에 작성!</p>
</blockquote>
<div style="background-color:#EDF3F7;padding:10px 20px;border-radius:10px;margin-top:40px;">

<pre><code>@sync_to_async
@router.get(&quot;/test&quot;, response=TestSchema)
def test_func(request, input):
    ...</code></pre></div>



<h3 id="참고">참고</h3>
<p><a href="https://docs.djangoproject.com/en/4.1/topics/async/">https://docs.djangoproject.com/en/4.1/topics/async/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Python] gunicorn, uvicorn(wsgi, asgi)]]></title>
            <link>https://velog.io/@so-eun/Python-gunicorn-uvicornwsgi-asgi</link>
            <guid>https://velog.io/@so-eun/Python-gunicorn-uvicornwsgi-asgi</guid>
            <pubDate>Sun, 13 Nov 2022 08:17:39 GMT</pubDate>
            <description><![CDATA[<h3 id="gunicorn-이란-무엇일까">Gunicorn 이란 무엇일까?</h3>
<p>Django Framework 기반의 백엔드 어플리케이션을 위한 <strong>WSGI</strong> 서버이다.</p>
<blockquote>
<p>그럼 <strong>WSGI</strong>는 뭘까?
👉WSGI <strong>(Web Server Gateway Interface)</strong>: Python 웹 어플리케이션이 웹 서버와 통신을 하기 위한 인터페이스이다. 웹 서버의 요청을 받아 Python 어플리케이션에 전달하는 역할을 한다.</p>
</blockquote>
<p>이러한 WSGI의 대표적인 예 중 하나가 Gunicorn이다.
이 외에도, uWSGI, mode_wsgi, CherryPy 등이 있으며, 아래 이미지와 같이 웹 서버와 어플리케이션 서버 사이의 요청을 처리하는 역할을 한다.
<img src="https://velog.velcdn.com/images/so-eun/post/1d5ced11-9d14-4888-a51e-7b2daf1777a0/image.png" alt="">이미지 출처: <a href="https://leffept.tistory.com/345">https://leffept.tistory.com/345</a></p>
<p>이렇게 파이썬 어플리케이션의 실행을 도와주는 WSGI에도 단점이 있다.
WSGI는 요청을 받고 응답을 반환하는 동작이 <strong><em>단일 동기 호출 방식으로 처리</em></strong> 된다는 점이다. 오랜 시간 연결을 유지해야 하는 Websocket이나 긴 HTTP 요청을 처리하기에 이 방식이 적합하지 않다.</p>
<p>이와 같은 단점을 보완하고자 등장한 것이 <span style="color:green"><strong>ASGI(Asynchronous Server Gateway Interface)</strong></span> 이다!
WSGI보다 나중에 나온 인터페이스로 WSGI의 단점을 보완하고, <span style="color:purple">비동기 코드를 처리하는 데에 효과적</span>이다.
클라이언트로부터 여러 이벤트를 주고 받을 수 있고, 백그라운드 코루틴을 실행할 수 있다.</p>
<h3 id="이-쯤에서-uvicorn을-알아보자">이 쯤에서, Uvicorn을 알아보자!</h3>
<p>앞서 설명한 비동기 인터페이스를 통해 구현된 ASGI 웹 서버가 uvicorn이다.
<strong>단일 프로세스</strong>에서 uvloop 기반 비동기 Python code를 실행한다. 
하지만, 단일 프로세스이기 때문에 서버 성능 저하가 발생할 수 있다. 
그래서 우리는 <span style="color:green;font-weight:bold;font-size:20px;">웹 서버이자 프로세스 관리자인 gunocorn을 활용해 여러 개의 uvicorn을 관리하여 멀티 프로세스 환경을 구성</span>한다.</p>
<p>아래의 구동 예시를 살펴보자.</p>
<pre><code>gunicorn main:app --workers 3 --worker-class uvicorn.workers.UvicornWorker --daemon --access-logfile ./log.log</code></pre><p>--workers : 프로세스 개수 (최대 vcpu 개수 * 2만큼 설정을 권장)
--worker-class : 프로세스를 다중으로 실행하기 위해 필요한 옵션
--daemon : 백그라운드로 실행
--access-logfile ./access_log.log : access_log.log 이름으로 로그를 저장</p>
<hr>
<p>구동 방법은 알겠다. 그런데 왜 멀티 프로세스로 돌리는거지?
위에 작성한 내용과 관련이 있다. 하나의 요청을 받고, 해당 요청을 수행하는 과정이 오래 걸리면, 그 시간 동안 다른 요청을 처리하지 못하게 된다. 실제 서비스하는 환경에서 이러한 이슈가 발생한다면...<span style="font-size:40px;">🤦‍♀️</span>
worker의 개수를 늘려 여러 개로 병렬 처리를 한다면 위와 같은 문제는 발생하지 않을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] ORM 'prefetch_related'와 'select_related']]></title>
            <link>https://velog.io/@so-eun/Django-ORM-prefetchrelated%EC%99%80-selectrelated</link>
            <guid>https://velog.io/@so-eun/Django-ORM-prefetchrelated%EC%99%80-selectrelated</guid>
            <pubDate>Tue, 18 Oct 2022 04:30:38 GMT</pubDate>
            <description><![CDATA[<p>편리하면서도 은근히 사용하기 복잡한 Django의 ORM,
그 중 prefetch_related와 select_related에 대해 자세히 알아보자.</p>
<h3 id="먼저-orm이란-무엇일까">먼저, ORM이란 무엇일까?</h3>
<p><span style="background-color:lightgray"><strong>O</strong>bject <strong>R</strong>elational <strong>M</strong>apping, 객체-관계 매핑</span>을 뜻하는 용어로, 객체와 관계형 데이터베이스의 데이터를 자동으로 연결해주는 것을 의미한다.</p>
<hr>
<h3 id="👉-select_related--정참조">👉 <strong>select_related : 정참조</strong></h3>
<p><span style="color:green;font-weight:bold">Foreign Key 속성이 있는 객체에서 해당 부모를 참조하거나 1:1 관계에서 참조하는 경우</span></p>
<p>Query문의 Join을 사용해 foreign-key(one to one, many to one)를 참조하며, 관련된 테이블 정보를 받아올 수 있다.</p>
<p>다음의 예시와 함께 확인해보자.
&quot;나는 user id가 1인 사람의 도시 명을 알고 싶다.&quot;
<span style="color:green"><strong>(User: 자식 테이블, City: 자식이 참조하는 부모 테이블)</strong></span></p>
<pre><code># 도시 정보
class City(models.Model):
    id = models.BigAutoField(primary_key=True)
    name = models.CharField(max_length=20)

# 유저 정보
class User(models.Model):
    id = models.BigAutoField(primary_key=True)
    city = models.ForeignKey(&#39;City&#39;, on_delete=models.CASCADE)
    name = models.CharField(max_length=20)
    age = models.SmallIntegerField()</code></pre><blockquote>
<ol>
<li>select_related 사용 <strong>X</strong></li>
</ol>
</blockquote>
<pre><code>  city_info = User.objects.get(id=1).city
  city_name = city_info.name</code></pre><blockquote>
<ol start="2">
<li>select_related 사용 <strong>O</strong></li>
</ol>
</blockquote>
<pre><code>  city_info = User.objects.select_related(&#39;city&#39;).get(id=1).city
  city_name = city_info.name</code></pre><p>위의 1과 2 방법은 쿼리 상으로는 다를 게 없어 보인다. 오히려 2번이 더 복잡해 보이기도 한다. 그렇다면, DB 접근 방식을 비교해보자.</p>
<p>1번의 경우, <span style="color:skyblue;font-weight:bold">DB에 2번의 접근</span>을 하게 된다.
User 테이블에서 id가 1번인 유저의 city 값을 가져오기 위한 조회 -- (1),
가져온 1의 데이터를 바탕으로 City 테이블을 조회해 name 값을 가져오기 위한 조회 -- (2)</p>
<p>하지만, select_related를 사용한 2번의 경우 <span style="color:skyblue;font-weight:bold">DB에 1번만 접근</span>한다.
select_related에서 관련된 Object(여기서는 city)까지 <strong>한 번에 조회해 cache에 저장</strong>한다.
따라서, 이후에 city 값에 대한 조회를 할 때는 DB에 재접근 방식이 아닌 가져온 cache 값에서 꺼내서 사용하기만 하면 된다.</p>
<blockquote>
</blockquote>
<p>➰ select_related는 <strong>INNER JOIN</strong>을 통해 데이터를 가져온다.
➰ foreign-key, one-to-one과 같은 <strong>1:1 관계</strong>에서 사용하는 것이 효과적이다.</p>
<hr>
<h3 id="👉-prefetch_related--정참조-역참조-모두">👉 <strong>prefetch_related : 정참조, 역참조 모두</strong></h3>
<p><span style="color:green;font-weight:bold">정참조를 포함하여, 역참조(다른 객체가 ForeignKey를 가지고 있거나 M:N 관계일 때, 해당 객체를 참조하고 있는 다른 객체를 참조)하는 경우 사용한다.</span></p>
<blockquote>
</blockquote>
<p>➰ prefetch_related는 foreign-key, one-to-one 뿐만 아니라 <strong>many-to-many, many-to-one 관계</strong>에서도 사용 가능하다.
➰ SQL에서 각 모델을 조회하고, Python에서 Join을 실행한다.</p>
<p><span style="background-color:#FFFAF0;color:#FF1493;padding:3px"><strong>간단한 테이블 및 구조로 확인해 보았지만, 데이터가 많아지고 연관된 테이블이 많아질 경우 DB 접근 방식에 따른 쿼리 성능은 무시할 수 없다.</strong></span></p>
<p>상황에 맞게 select_related와 prefetch_related를 사용해 효율적인 코딩을 해보자!😆</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Git 세팅 및 자주 사용하는 명령어]]></title>
            <link>https://velog.io/@so-eun/Git-Git-%EC%84%B8%ED%8C%85-%EB%B0%8F-%EB%A1%9C%EC%BB%AC-%EC%9B%90%EA%B2%A9-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@so-eun/Git-Git-%EC%84%B8%ED%8C%85-%EB%B0%8F-%EB%A1%9C%EC%BB%AC-%EC%9B%90%EA%B2%A9-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Tue, 18 Oct 2022 00:45:18 GMT</pubDate>
            <description><![CDATA[<ul>
<li><strong>git 저장소 초기화</strong>
git init</li>
</ul>
<ul>
<li><strong>필요에 따라 git config 세팅</strong>
git config (–global) user.name “[user-name]”
git config (–global) user.email [<a href="mailto:email@address.com">email@address.com</a>]</li>
</ul>
<ul>
<li><p><strong>git 저장소 로컬로 복제</strong>
git clone <a href="https://github.com/.git">https://github.com/.git</a> (public)
git clone https://[user-name]@github.com/.git (private)</p>
</li>
<li><p><strong>로컬 &lt;-&gt; 원격 저장소 연결</strong>
git remote -v
git remote add origin github.com/*.git</p>
</li>
<li><p><strong>브랜치 생성 후 이동</strong>
git checkout -b [branch]</p>
</li>
<li><p><strong>브랜치 삭제</strong>
git branch -d [branch]</p>
</li>
<li><p><strong>원격 저장소 pull</strong>
git pull origin [branch]</p>
</li>
<li><p><strong>변경사항 저장</strong>
git commit -m &#39;[commit message]&#39;
  <em>-m</em>: 옵션 (커밋 메세지 지정)</p>
</li>
<li><p><strong>원격 저장소 push</strong>
git push origin [branch]</p>
</li>
<li><p><strong>현재 브랜치에 다른 브랜치 수정사항 병합</strong>
git merge [another branch]</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA['클린 아키텍처(Clean Architecture)']]></title>
            <link>https://velog.io/@so-eun/%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98Clean-Architecture</link>
            <guid>https://velog.io/@so-eun/%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98Clean-Architecture</guid>
            <pubDate>Sun, 16 Oct 2022 13:57:57 GMT</pubDate>
            <description><![CDATA[<p>🖥<strong>좋은 코드</strong>란 무엇일까?
프로그래밍을 하면서 항상 고민하는 주제이다. 개발자라면 누구나 효율적이고 깔끔한 코딩을 하고 싶어 할 것이다.</p>
<p>예를 들어, 우리가 작업한 프로젝트에 버그가 있어 수정을 해야 하는 상황이라고 가정하자.
버그 하나를 잡기 위해 여러 개의 파일을 수정하며 해당 기능과 연관된 모든 파일을 수정해야 한다면 그것은 클린한 코드일까?
시간과 함께 효율성도 매우 떨어지는 작업이 될 것 같다. 위와 같이 작업되어 있다면 아마 코드의 재사용성도 그리 높진 않을 것이다.
이러한 점들을 해결하고, 보다 효율적인 프로그래밍을 위해 클린 아키텍처 패턴을 적용하도록 한다.</p>
<hr>
<h3 id="clean-architecture--클린-아키텍처">Clean Architecture : 클린 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/so-eun/post/4b869591-d07b-4de2-9345-2194fe2d97d0/image.png" alt="">이미지 출처: <a href="http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html</a></p>
<p><strong>Clean + Architecture</strong> : 깔끔한 구조
말 그대로 깔끔한 구조의 디자인 패턴이며, 위 그림의 화살표 방향(External Interfaces -&gt; Infrastructure -&gt; Use Case -&gt; Entity)은 의존성(Dependency)의 방향을 의미한다.</p>
<p>그림을 안쪽부터 순서대로 보면 다음과 같다.</p>
<ol>
<li><strong>Entity</strong> : 엔티티</li>
</ol>
<ul>
<li>핵심 업무 규칙을 캡슐화한다.</li>
<li>변형이 가장 적고, 외부의 영향을 최소화하는 영역이다.</li>
</ul>
<ol start="2">
<li><strong>Use Case</strong> : 유스케이스</li>
</ol>
<ul>
<li>어플리케이션의 비즈니스 규칙이 포함될 수 있다.</li>
<li>사용자의 요청에 알맞게 상위 계층(Entity)를 가공, 출력한다.</li>
</ul>
<ol start="3">
<li><strong>Infrastructure</strong> : 인터페이스 어댑터(컨트롤러, 게이트웨이, 프레젠터)</li>
</ol>
<ul>
<li>데이터 형식을 변환하는 역할이다.</li>
<li>외부 플랫폼에 따라 내부 비즈니스 로직은 유지한 채 어댑터의 로직 일부만 수정해 편리하게 개발 가능하도록 도와준다.</li>
</ul>
<ol start="4">
<li><strong>External Interfaces</strong> : 외부 영역 (DB, API, UI, Devices..)</li>
</ol>
<ul>
<li>웹 혹은 앱의 프레임워크로서, 내부 비즈니스 로직과 직접적인 영향은 없는 영역이다.</li>
<li>언제든 변경이 가능하다.</li>
</ul>
<blockquote>
<p>📌 핵심은 <strong>내부</strong>이다. 
📌 내부 영역으로 갈수록 <strong>변경이 적어야</strong> 한다.
📌 외부에서는 내부 영역에 의존할 수 있지만, 내부에서는 외부 영역으로의 의존성이 적용될 수 없다. 
    ex) Use Case는 Entity를 사용할 수 있어도, Entity는 Use Case를 사용 불가</p>
</blockquote>
<hr>
<h3 id="ddddomain-driven-design--도메인-주도-설계">DDD(Domain-Driven Design) : 도메인 주도 설계</h3>
<p>도메인을 중심으로 설계해 나가는 방법이다.
여기서의 &#39;도메인&#39;은 사용자가 필요한 모든 비즈니스 영역, 업무의 집합이다. 
주문 사이트를 예로 들었을 때, 주문, 배송, 마케팅은 각각의 도메인에 해당한다고 볼 수 있다.
이러한 <strong><u>도메인들이 서로 상호작용을 하며 설계하는 방식</u></strong>을 도메인 주도 설계라고 한다. </p>
<blockquote>
<p>📌 각각의 도메인은 서로 <strong>철저히 분리</strong>된다.
📌 핵심은 &quot;<strong>Loose Coupling, High Cohesion</strong>&quot;, 높은 응집력과 낮은 결합도로 <strong>변경과 확장에 용이</strong>한 설계를 얻게된다.</p>
</blockquote>
<p>정리하면, 도메인을 각 서비스별로 분리하는게 핵심이다. 하지만 작업자에 따라, 사용자의 보는 관점에 따라 도메인은 달라질 수 있다. 따라서 정해진 답은 없다.</p>
]]></description>
        </item>
    </channel>
</rss>