<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>techy-yunong.log</title>
        <link>https://velog.io/</link>
        <description>좋아서 시작한 개발 지금은...</description>
        <lastBuildDate>Wed, 10 Nov 2021 12:43:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>techy-yunong.log</title>
            <url>https://images.velog.io/images/techy-yunong/profile/c4270454-6bb1-4dba-90d4-da5de2500923/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. techy-yunong.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/techy-yunong" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Kafka CLI 사용하기]]></title>
            <link>https://velog.io/@techy-yunong/use-kafka-cli</link>
            <guid>https://velog.io/@techy-yunong/use-kafka-cli</guid>
            <pubDate>Wed, 10 Nov 2021 12:43:43 GMT</pubDate>
            <description><![CDATA[<p>Kafka CLI에한 설명과 예제를 정리해나가는 곳이다. 앞으로도 새로운 kafka cli에 대한 새로운 지식을 알게 될 경우 이 곳에 추가해 나갈 예정이다.</p>
<h1 id="topic">Topic</h1>
<h2 id="topic-목록-보기">topic 목록 보기</h2>
<pre><code class="language-bash">./kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list </code></pre>
<h2 id="topic-생성">topic 생성</h2>
<pre><code class="language-bash">./kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --create \
  --topic my-topic --partitions 6 --replication-factor 1</code></pre>
<p><code>min.insync.replicas</code> 와 같은 topic config를 기본값이나 broker의 값을 override 하여 지정하고 싶다면 <code>--config</code> 옵션을 사용한다.</p>
<pre><code>kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --create \
  --topic my-topic --partitions 6 --replication-factor 1 --config min.insync.replicas=1</code></pre><h2 id="topic-상세-조회">topic 상세 조회</h2>
<pre><code>./kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --topic my-topic --describe</code></pre><p>결과는 다음과 같다. partition의 갯수, partition의 leader, replica, in-sync replica broker id 정보를 얻을 수 있다.</p>
<pre><code>Topic:my-topic    PartitionCount:6    ReplicationFactor:1    Configs:
    Topic: my-topic    Partition: 0    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 1    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 2    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 3    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 4    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 5    Leader: 1    Replicas: 1    Isr: 1</code></pre><h2 id="topic-설정-변경">topic 설정 변경</h2>
<p>파티션 및 리플리카 수 변경, config 변경등을 할 수 있다. 파티션 수는 현재 수에서 증가만 할 수 있다. </p>
<pre><code>./kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --alter --topic my-topic --partitions 9</code></pre><p>결과를 보면 정상적으로 변경된 것을 볼 수 있다.</p>
<pre><code>./kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --describe --topic my-topic
Topic: my-topic    TopicId: gfcRYfJ5QW-pubajGhAelw    PartitionCount: 9    ReplicationFactor: 1    Configs:
    Topic: my-topic    Partition: 0    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 1    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 2    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 3    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 4    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 5    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 6    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 7    Leader: 1    Replicas: 1    Isr: 1
    Topic: my-topic    Partition: 8    Leader: 1    Replicas: 1    Isr: 1</code></pre><p>파티션수 변경은 키를 사용하는 경우 키별로 저장되는 파티션의 위치가 바뀌면서 ordering 등에 문제가 생길 수 있어 주의해야 한다.</p>
<h2 id="topic-삭제">topic 삭제</h2>
<pre><code>./kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --delete --topic my-topic</code></pre><p>topic 삭제는 broker 설정에서 <code>delete.topic.enable=true</code> 로 되어 있어야 가능하다.</p>
<h1 id="producer--consumer-console">Producer &amp; Consumer console</h1>
<p>kafka producer, consumer를 cli로 간단히 사용할 수 있게 해주는 cli 이다.
<code>kakfa-console.producer.sh</code> 를 사용하여 메세지를 kafka로 보낼 수 있다.</p>
<pre><code>./kafka-console-producer.sh --broker-list 127.0.0.1:9092 --topic my-topic
&gt;hi
&gt;this is kafka</code></pre><p><code>--producer-property</code>를 사용하면 <code>acks</code> 의 레벨 설정도 가능하다.</p>
<pre><code>./kafka-console-producer.sh --broker-list 127.0.0.1:9092 --topic my-topic acks=all</code></pre><p><code>kafka-console-consumer.sh</code> 를 사용하면 kafka에 저장되어 있는 로그를 받을 수 있다.</p>
<pre><code>./kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic my-topic
hi
this is kafka</code></pre><p><code>--from-beginning</code> 을 사용하면 로그를 처음부터 가져올 수 있다.</p>
<h2 id="key-사용하기">key 사용하기</h2>
<p>key를 포함하여 로그를 보내고 싶다면 다음과 같이 실행하면 된다.</p>
<pre><code>./kafka-console-producer.sh --broker-list 127.0.0.1:9092 --topic my-topic --property parse.key=true --property key.separator=,
&gt;1,jason
&gt;2,mike</code></pre><p>consumer 쪽에서도 key를 보기 위해서는 <code>--property print.key=true</code> 를 사용하면 확인할 수 있다.</p>
<pre><code>./kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic my-topic --property print.key=true --property key.separator=,
1,jason
2,mike</code></pre><h2 id="consumer-group의-사용">consumer group의 사용</h2>
<p><code>--group</code> 옵션으로 consumer group을 지정할 수 있다. consumer group이 존재하지 않는다면 consumer-group을 새로 생성한다. 이 경우 <code>--from-beginning</code> 옵션을 사용하지 않는다면 새로 들어오는 데이터부터 조회된다. </p>
<p>만약 <code>--group</code> 옵션으로 지정되는 consumer group이 이미 존재한다면, 이전에 커밋한 offset 이후의 메세지만 가져오게 된다. 이 경우 <code>--from-beginning</code> 옵션은 무시된다.</p>
<pre><code>./kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic my-topic --group my-first-group</code></pre><h1 id="consumer-group-관리">Consumer Group 관리</h1>
<p><code>kafka-consumer-console.sh</code> 에서는 consumer group 생성을 해주고, 메세지를 받는 역할을 하지만 consumer group을 관리하지는 못한다. consumer group에 대한 미세한 조회 및 관리는 <code>kafka-consumer-groups.sh</code> 로 할 수 있다.</p>
<h2 id="consumer-group-리스트-조회">consumer group 리스트 조회</h2>
<pre><code>./kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --list
my-first-group</code></pre><h2 id="consumer-group의-상세-조회">consumer group의 상세 조회</h2>
<pre><code>./kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --describe --group my-first-group


GROUP           TOPIC           PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             CONSUMER-ID                                                    HOST            CLIENT-ID
my-first-group  my-topic        2          3               3               0               consumer-my-first-group-1-d581edd4-1fd0-4d53-9c8a-eef0af577cd8 /172.18.0.1     consumer-my-first-group-1
my-first-group  my-topic        1          2               2               0               consumer-my-first-group-1-d581edd4-1fd0-4d53-9c8a-eef0af577cd8 /172.18.0.1     consumer-my-first-group-1
my-first-group  my-topic        5          0               0               0               consumer-my-first-group-1-d581edd4-1fd0-4d53-9c8a-eef0af577cd8 /172.18.0.1     consumer-my-first-group-1
my-first-group  my-topic        0          4               4               0               consumer-my-first-group-1-d581edd4-1fd0-4d53-9c8a-eef0af577cd8 /172.18.0.1     consumer-my-first-group-1
my-first-group  my-topic        4          1               1               0               consumer-my-first-group-1-d581edd4-1fd0-4d53-9c8a-eef0af577cd8 /172.18.0.1     consumer-my-first-group-1
my-first-group  my-topic        3          7               7               0               consumer-my-first-group-1-d581edd4-1fd0-4d53-9c8a-eef0af577cd8 /172.18.0.1     consumer-my-first-group-1
</code></pre><p>consumer group의 파티션별 offset, lagging 정도, 접속된 consumer id, consumer host 정보 등 매우 상세한 정보를 확인할 수 있다.</p>
<h2 id="consumer-group의-consumer-정보-조회">consumer group의 consumer 정보 조회</h2>
<p>현재 어떤 consumer가 conumser group에 포함되어있고, 얼마만큼의 파티션을 담당하고 있는지 간단한 정보를 확인할 수 있다.</p>
<pre><code>./kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --describe --group my-first-group --members

GROUP           CONSUMER-ID                                                    HOST            CLIENT-ID                 #PARTITIONS
my-first-group  consumer-my-first-group-1-f3b03fb1-c65c-4741-a52d-30b701a0787e /172.18.0.1     consumer-my-first-group-1 3
my-first-group  consumer-my-first-group-1-3ad52cf3-4e00-4a29-a549-40506bb5f1ed /172.18.0.1     consumer-my-first-group-1 3</code></pre><h2 id="consumer-group-설정-변경">consumer group 설정 변경</h2>
<p>만약 consumer-group의 topic에 대한 offset을 초기화 시키고 싶다면 다음 명령어를 사용하면 된다. consumer는 모두 inactive 상태여야 실행이 가능하다.</p>
<pre><code>./kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --group my-first-group --topic my-topic --to-earliest --reset-offsets --execute

GROUP                          TOPIC                          PARTITION  NEW-OFFSET
my-first-group                 my-topic                       2          0
my-first-group                 my-topic                       1          0
my-first-group                 my-topic                       5          0
my-first-group                 my-topic                       0          0
my-first-group                 my-topic                       4          0
my-first-group                 my-topic                       3          0</code></pre><p>consumer-group의 current-offset을 확인해보면 모두 0으로 바뀐것을 알 수 있다.</p>
<pre><code>./kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --describe --group my-first-group

Consumer group &#39;my-first-group&#39; has no active members.

GROUP           TOPIC           PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             CONSUMER-ID     HOST            CLIENT-ID
my-first-group  my-topic        2          0               3               3               -               -               -
my-first-group  my-topic        1          0               2               2               -               -               -
my-first-group  my-topic        5          0               0               0               -               -               -
my-first-group  my-topic        0          0               4               4               -               -               -
my-first-group  my-topic        4          0               1               1               -               -               -
my-first-group  my-topic        3          0               7               7               -               -</code></pre><p><code>dry-run</code> 옵션도 제공한다. 이 경우 <code>--execute</code> 는 사용하지 않는다.</p>
<pre><code>./kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --group my-first-group --topic my-topic --to-earliest --reset-offsets --dry-run</code></pre><h2 id="consumer-group의-삭제">consumer group의 삭제</h2>
<p>consumer 연결이 안된 상태에서 다음 명령어를 실행하면 consumer group이 삭제된다.</p>
<pre><code>./kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --delete --group my-first-group
Deletion of requested consumer groups (&#39;my-first-group&#39;) was successful.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[AWS ASG Predictive Scaling 작동 원리 파헤치기: Load Metric, Scaling Metric의 이해]]></title>
            <link>https://velog.io/@techy-yunong/AWS-ASG-Predictive-Scaling-Metric</link>
            <guid>https://velog.io/@techy-yunong/AWS-ASG-Predictive-Scaling-Metric</guid>
            <pubDate>Wed, 13 Oct 2021 11:45:52 GMT</pubDate>
            <description><![CDATA[<p>AWS Auto Scaling Group (이하 ASG)를 사용하는 이유 중 하나는 들어오는 로드가 증가할 때 유연하게 인스턴스 수(capacity)를 늘려 인스턴스 당 처리량을 적정 수준으로 유지하여 연결된 서비스에 주는 영향을 최소화 하거나, 인스턴스 수에 비해서 로드가 적다면 인스턴스 수를 줄여 낭비되는 리소스를 최소화하기 위해서이다.</p>
<p>이를 위해 ASG에서는 어떤 조건에서 capacity를 조정하도록 하도록 하는 기능을 제공하는 Scaling Policy를 제공한다. 종류로는 Dynamic Scaling policy, Predictive Scaling policy, Scheduled Action policy 등이 있다. 이번 글에서는 이 중에 Predictive Scaling Policy의 작동 원리 추론을 해보고자 한다.</p>
<p>Predictive Scaling Policy는 과거의 CPU 사용량, 네트워크 유입량 등의 로드의 과거 기록을 이용하여 미래에 특정 시점에 적용되어야 할 ASG의 capacity를 기계 학습(Machine Learning)을 통해 예측을 해준다. 가장 중요한 설정값은 Metrics and target utilization이며, 내용을 살펴보면 다음과 같다.</p>
<ul>
<li>Metric: 메트릭은 크게 두가지로 나누어진다. 첫번째 메트릭은 <strong>Load Metric</strong>으로 <em>전체 사용량 추정</em>에 사용하는 메트릭이다. 두번째 메트릭은 <strong>Scaling Metric</strong>으로 capcity 추측을 위해 사용하는 <strong>인스턴스당 사용량의 평균</strong> 메트릭이며, Target Utilization 값도 이 메트릭 기준으로 정한다. 
선택할 수 있는 메트릭은 다음과 같으며, Load Metric과 Scaling Metric을 다르게 선택하는 Custom metric pair 옵션도 제공한다.<ul>
<li>CPU Utilization: Load Metric에서는 전체 CPU 사용량, Scaling 메트릭에서는 인스턴스당 CPU 사용량의 평균</li>
<li>Network In (bytes): Load Metric에서는 전체 네트워크 유입량, Scaling 메트릭에서는 인스턴스당 메트릭 유입량의 평균</li>
<li>Network Out (bytes): Load Metric에서는 전체 네트워크 송출량, Scaling 메트릭에서는 인스턴스당 메트릭 송출량의 평균</li>
<li>Application Load Balancer request count: ALB에 연결된 경우만 사용 가능. Load Metric에서는 전체 request count, Scaling 메트릭에서는 인스턴스 당 request count의 평균</li>
</ul>
</li>
<li>Target Utilization: Scaling Metric의 유닛을 사용하며, 이 수치를 유지하도록 capacity 값이 예측된다. e.g. CPU Utilization의 경우 40%</li>
</ul>
<p><img src="https://images.velog.io/images/techy-yunong/post/16184150-916d-4dd7-ad22-dd4f61ee4a51/image.png" alt=""></p>
<p>Predictive Scaling에 대해서 <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html">AWS 메뉴얼</a>을 보다보니 Load Metric과 Scaling Metric이 각각 어떤 역할을 하는지 헷갈렸으며, 특히 두 가지 종류를 다르게 설정할 수 있는 Custom Metric Pair 에 대해서는 더욱 이해가 안갔었다. 자료를 찾아보았지만 정확히 원리는 설명해주는 곳은 없어 내가 가진 약간의 ML 지식으로 나라면 이렇게 개발했을 거라 추측해 보았다.</p>
<h1 id="단일-metric-사용-시">단일 Metric 사용 시</h1>
<h2 id="load-metric의-사용">Load Metric의 사용</h2>
<p>우선 과거 사용량을 기준으로 미래의 특정 시점의 사용량을 예측해야 한다는 것쯤은 쉽게 추측할 수 있다. 이 때 사용하는 메트릭이 Load Metric이다. 예를 들어 CPU 전체 사용량을 Load Metric으로 선택한 경우, 과거의 특정 시간, 특정 요일의 CPU 전체 사용량을 학습하여, 미래의 특정 시점의 사용량을 예측하도록 한다. 학습하는 데이터를 간단히 적어보면 (학습 시 윈도우는 10분으로 가정한다.)</p>
<table>
<thead>
<tr>
<th>요일(x1)</th>
<th>시간(x2)</th>
<th>CPU 사용량(y)</th>
</tr>
</thead>
<tbody><tr>
<td>월요일</td>
<td>14:00</td>
<td>2000</td>
</tr>
<tr>
<td>월요일</td>
<td>14:10</td>
<td>2050</td>
</tr>
<tr>
<td>월요일</td>
<td>14:20</td>
<td>2100</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</tbody></table>
<p>이런 데이터가 충분히 모여서 학습하면 특정 시점의 CPU 사용량 예측을 할 수 있을 것이다.</p>
<table>
<thead>
<tr>
<th>요일(x1)</th>
<th>시간(x2)</th>
<th>CPU 사용량(y)</th>
</tr>
</thead>
<tbody><tr>
<td>월요일</td>
<td>15:00</td>
<td>?</td>
</tr>
</tbody></table>
<p>이렇게 예측한 Load Metric은 이후에 Scaling Metric 과 함께 capacity를 예측하는데 사용된다.</p>
<h2 id="scaling-metric의-사용">Scaling Metric의 사용</h2>
<p>Load Metric을 통해서 미래의 CPU 전체 사용량까지 마친 시점에서 Scaling Metric은 어떻게 사용할 수 있을까 고민을 해보았다. 다음과 같이 학습 데이터를 준비해 학습한다면 특정 로드에 대한 Target Utilization에 해당하는 capacity 값을 추측할 수 있을 것이다.</p>
<table>
<thead>
<tr>
<th>Load Metric(x1)</th>
<th>Scaling Metric 값(x2)</th>
<th>capacity(y)</th>
</tr>
</thead>
<tbody><tr>
<td>2000</td>
<td>20</td>
<td>4</td>
</tr>
<tr>
<td>2050</td>
<td>21</td>
<td>4</td>
</tr>
<tr>
<td>2100</td>
<td>18</td>
<td>5</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</tbody></table>
<p>이 데이터를 학습을 했다면, 미래의 특정 시점에 Load Metric 데이터와 Target Utilization 값을 통해 capacity 값을 파악할 수 있다. Target Utilization을 40%라 한다면 다음 데이터 입력과 학습된 모델을 통하여 capacity 값을 예측할 수 있을 것이다.</p>
<table>
<thead>
<tr>
<th>Load Metric(x1)</th>
<th>Scaling Metric 값(x2)</th>
<th>capacity(y)</th>
</tr>
</thead>
<tbody><tr>
<td>2000</td>
<td>40</td>
<td>?</td>
</tr>
</tbody></table>
<h1 id="custom-metric-pair-사용-시">Custom Metric Pair 사용 시</h1>
<p>단일 Metric 사용 시, Load Metric, Scaling Metric 설명을 통하여 Custom Metric Pair 가 어떠한 방식으로 작동할 지 예측해 볼 수 있다. Load Metric은 Network In 값을 사용하고, Scaling Metric은 CPU Utilization을 사용한 경우를 살펴보자. Target Utlization 값은 40%이라 가정한다.</p>
<h2 id="load-metric의-사용-1">Load Metric의 사용</h2>
<p>단일 Metric 을 사용할 때와 동일하다.</p>
<table>
<thead>
<tr>
<th>요일(x1)</th>
<th>시간(x2)</th>
<th>Network In 총량(y)</th>
</tr>
</thead>
<tbody><tr>
<td>월요일</td>
<td>14:00</td>
<td>40000</td>
</tr>
<tr>
<td>월요일</td>
<td>14:10</td>
<td>41000</td>
</tr>
<tr>
<td>월요일</td>
<td>14:20</td>
<td>42000</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</tbody></table>
<p>이러한 데이터의 학습을 통해 생성된 모델을 통해 특점 시점의 Network In 총량을 예측할 수 있다.</p>
<table>
<thead>
<tr>
<th>요일(x1)</th>
<th>시간(x2)</th>
<th>Network In 총량(y)</th>
</tr>
</thead>
<tbody><tr>
<td>월요일</td>
<td>15:00</td>
<td>?</td>
</tr>
</tbody></table>
<h2 id="scaling-metric의-사용-1">Scaling Metric의 사용</h2>
<p>단일 Metric과는 다르게 Load Metric과 Scaling Metric의 단위가 다르다.</p>
<table>
<thead>
<tr>
<th>Load Metric(x1)</th>
<th>Scaling Metric 값(x2)</th>
<th>capacity(y)</th>
</tr>
</thead>
<tbody><tr>
<td>40000</td>
<td>20</td>
<td>4</td>
</tr>
<tr>
<td>41000</td>
<td>21</td>
<td>4</td>
</tr>
<tr>
<td>42000</td>
<td>18</td>
<td>5</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</tbody></table>
<p>이를 통해 다음 입력과 학습된 모델을 통해 특정 시간의 Target Utilization에 해당하는 capacity 값을 예측할 수 있다.</p>
<table>
<thead>
<tr>
<th>Load Metric(x1)</th>
<th>Scaling Metric 값(x2)</th>
<th>capacity(y)</th>
</tr>
</thead>
<tbody><tr>
<td>40000</td>
<td>40</td>
<td>?</td>
</tr>
</tbody></table>
<h2 id="주의할-점">주의할 점</h2>
<p>단일 Metric은 Load Metric과 Scaling Metric이 동일하기 때문에 두 메트릭의 상관 관계가 비교적 Linear하게 유지되어 예측에 문제가 없을 것이다. 그러나 Custom Metric Pair 를 사용하게 된다면 Load Metric과 Scaling Metric의 상관 관계가 명확하지 않은 케이스가 있을 수 있어 주의가 필요하다.</p>
<p>예를 들어 웹 API 서비스를 제공하는 ASG의 경우, API 별로 네트워크 In 은 비교적 비슷한데, CPU 사용량은 매우 상이하다면, 10000이라는 Network In 양이 사용되는 동안 동일 capacity에 대한 CPU 평균 사용량은 어떤 경우는 10%, 어떤 경우는 50%가 되는 경우가 생긴다고 해보자. 이 경우 Custom Metric Pair를 상기 예제처럼 선택한다면, 제대로된 학습을 기대하기 힘들며 예측도 엉망이 될 가능성이 높다.</p>
<h1 id="아쉬운-점">아쉬운 점</h1>
<p>Predictive Scaling Policy를 알아보면서 아쉬운 점이 있었다. 학습 값으로 Load Metric을 하나밖에 선택할 수 없다는 것이다. 사실 Load Metric으로 제공해주는 모든 종류의 Metric을 학습 시 사용하더라도 모델을 만들기 위한 계산량이나 걸리는 시간은 크게 차이가 없을 것이라 예상되는데, 복수 선택을 할 수 있게 했다면 더 좋았을 것이란 생각이 든다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Ansible 첫 사용기]]></title>
            <link>https://velog.io/@techy-yunong/Ansible-first-time</link>
            <guid>https://velog.io/@techy-yunong/Ansible-first-time</guid>
            <pubDate>Wed, 08 Sep 2021 12:40:04 GMT</pubDate>
            <description><![CDATA[<p>최근에 서버에 공통으로 들어갈 스크립트를 돌리는 일이 생기고 있어, 편하게 이러한 작업을 할 수 없을까 보니 이전에 <a href="https://blog.gruntwork.io/why-we-use-terraform-and-not-chef-puppet-ansible-saltstack-or-cloudformation-7989dad2865c">IaC 관련 블로그</a>을 보면서 인상깊다고 여긴 Ansible이 생각났다.</p>
<p>Ansible은 인프라 구성 관리 툴로써 puppet과 chef와 비슷한 툴 이다. 회사에서는 chef 정도만 간접적으로 경험해 본게 전부지만 마스터 서버와 에이전트 설치가 필요 없어 관심을 가지게 되었다.</p>
<h1 id="ansible을-mac에-설치">Ansible을 Mac에 설치</h1>
<p>mac에서 설치는 역시 homebrew. 다음 명령어로 간단히 설치가 가능하다. 글쓰는 시점을 기준으로 설치 시, 4.5 버전이 설치되었다.</p>
<pre><code>brew install ansible</code></pre><h1 id="용어">용어</h1>
<p>Ansible에서 사용하는 주요 용어는 다음과 같다.</p>
<ul>
<li><p><strong>Control Node</strong>: Ansible이 설치된 머신을 이야기 한다.</p>
</li>
<li><p><strong>Managed Node</strong>: Ansible을 사용하여 관리할 서버 (네트워크)를 이야기 한다. hosts라고도 불린다. 위에서도 이야기 하였지만, managed node에 에이전트를 별도로 설치할 필요가 없다.</p>
</li>
<li><p><strong>Inventory</strong>: manage할 서버에 대한 정보를 모아놓은 파일이다. hostfile이라고도 불린다. ip 주소 등 접근 정보를 저장한다.</p>
</li>
<li><p><strong>PlayBook</strong>: ansible이 실행하는 task의 ordered-list 집합이다.</p>
</li>
</ul>
<h1 id="가장-빠르게-실행해보기">가장 빠르게 실행해보기</h1>
<p>ansible에서 관리할 서버 정보가 있는 inventory file을 작성해보자. 작업하는 파일이 위치하는 디렉토리를 하나 만든 후에 hosts.yml (이름은 상관 없다) 파일을 생성하고 다음과 같이 입력하면 <code>firstserver</code> 라는 alias로 접속할 서버를 정의할 수 있다. </p>
<pre><code>all:
  hosts:
    firstserver:
      ansible_port: 22
      ansible_host: &#39;3.35.0.208&#39;
      ansible_user: &#39;ubuntu&#39;
      ansible_ssh_private_key_file: &#39;/path/to/ssh_key&#39;</code></pre><p>이렇게 입력한 후 다음과 같이 접속 테스트를 해볼 수 있다.</p>
<pre><code>$ ansible all -m ping -i hosts.yml

firstserver | SUCCESS =&gt; {
    &quot;ansible_facts&quot;: {
        &quot;discovered_interpreter_python&quot;: &quot;/usr/bin/python3&quot;
    },
    &quot;changed&quot;: false,
    &quot;ping&quot;: &quot;pong&quot;
}</code></pre><p>자, 이제 접속 되는 것 까지는 확인을 하였으니, 이제 firstserver 에 파일을 하나 생성해보도록 ad-hoc 명령어를 실행해 보자. 아래와 같이 명령어를 실행하면 firstserver에 ansible.txt 파일 하나가 생성된 것을 확인할 수 있다. </p>
<pre><code>$ ansible -i hosts.yml firstserver -a &#39;touch ansible.txt&#39;

firstserver | CHANGED | rc=0 &gt;&gt;</code></pre><p>모듈 변수 지정이 없으면 ansible의 command module을 사용하게 되는데, piping이나 redirecting 같은 작업이 불가능하기 때문에 제한 사항이 많다. redirect를 통해 파일에 내용을 넣고 싶다면<code>ansible.builtin.shell</code> 모듈을 사용하면 된다. 아래 명령어를 실행하면 <code>welcome to ansible</code> 이라는 내용이 적혀 있는 <code>ansible.txt</code> 파일이 생성된다.</p>
<pre><code>$ ansible -i hosts.yml -m ansible.builtin.shell firstserver -a &#39;echo &quot;welcome to ansible&quot; &gt; ansible.txt&#39;

firstserver | CHANGED | rc=0 &gt;&gt;</code></pre><h1 id="iac-툴처럼-사용해보자">IaC 툴처럼 사용해보자</h1>
<p>지금까지 ad-hoc 명령어를 직접 실행하도록 해보았다. 그러나 IaC 툴로서의 제대로된 역할을 하려면 결국 이러한 명령어들이 재사용이 가능하도록 코드로 작성되어야 한다. 이때 사용하는 것이 playbook 이다. </p>
<p>playbook 파일(welcome-playbook.yml)을 다음과 같이 만들어보자.</p>
<pre><code>- name: create welcome file
  hosts: firstserver

  tasks:
    - name: create welcome file
      ansible.builtin.shell: &#39;echo &quot;welcome to ansible&quot; &gt; ansible.txt&#39;</code></pre><p>다음 명령어를 실행하면, 이전 ad-hoc 명령어와 동일한 결과를 낸다.</p>
<pre><code>$ ansible-playbook welcome-playbook.yml -i hosts.yml


PLAY [create welcome file] ********************************************************************

TASK [Gathering Facts] ************************************************************************
ok: [firstserver]

TASK [create welcome file] ********************************************************************
changed: [firstserver]

PLAY RECAP ************************************************************************************
firstserver                : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0</code></pre><h1 id="모듈module에-대해">모듈(module)에 대해</h1>
<p>현재까지는 <code>ansible.builtin.shell</code> 모듈만을 사용해 봤지만, ansible에는 정말 다양한 목적에 맞는 다양한 모듈이 있다. 파일 복사에 특화된 <code>ansible.builtin.copy</code> 와 같은 모듈부터 <a href="https://docs.ansible.com/ansible/latest/collections/community/aws/ec2_instance_module.html#ansible-collections-community-aws-ec2-instance-module">aws instance를 생성하거나 상태를 제어할 수 있는 모듈</a>까지 다양한 모듈을 제공한다. 앞으로 반복되는 업무에 지속적으로 사용해보며 유용한 팁이 있다면 공유할 예정이니 기대하시라.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS EventBridge 훑어 보기]]></title>
            <link>https://velog.io/@techy-yunong/AWS-EventBridge-concept</link>
            <guid>https://velog.io/@techy-yunong/AWS-EventBridge-concept</guid>
            <pubDate>Wed, 14 Jul 2021 13:00:49 GMT</pubDate>
            <description><![CDATA[<p><strong>EventBridge</strong>는 <strong>EDA(Event Driven Architecture)</strong>를 구성하는데 도움을 주는 AWS의 서버리스 서비스이다. AWS 서비스 뿐만 아니라 SaaS 서비스, 개인 어플리케이션에서 보내는 이벤트(데이터)를 받아, 원하는 SNS나 lambda와 같은 타겟으로 보내주는 역할을 한다.</p>
<h1 id="서비스-등장-배경">서비스 등장 배경</h1>
<p>AWS에서 설명하는 EventBridge 서비스가 생긴 배경에는 EDA를 서버리스 형태로 제공한다기 위해서라 말하면서, EDA가 복잡한 서비스 구조에서는 더 좋은 서비스간 통신 방식이라 이야기 한다.</p>
<h2 id="왜-eda가-더-좋은가-항상-그렇지는-않지만">왜 EDA가 더 좋은가? 항상 그렇지는 않지만...</h2>
<p>마이크로서비스 기반의 아키텍처에서 Direct Command 방식의 서비스간 통신을 할 경우, 전체 서비스 규모가 작을 때는 문제가 되지 않지만, 연결된 서비스 종류가 많아지고, 서비스간 종속 관계가 되는 깊이가 깊어지는 경우, 다음 두가지 문제가 발생한다. </p>
<ul>
<li>서비스가 많아질 수록 통신의 복잡성이 기하 급수적으로 높아진다. 단순히 서비스간 연결 구조가 복잡해지는 문제 뿐만 아니라, 서비스 API의 버저닝 문제까지 엮이면 훨씬 복잡해지단.</li>
<li>서버 to 서버 통신이 synchronous 하게 이루어질 경우, upstream의 서비스에 문제가 발생하면, downstream의 서비스에도 영향을 주어 더 넓은 장애를 유발할 수 있다.</li>
</ul>
<p>EDA의 경우는 서비스가 생성하는 이벤트를 다른 서비스가 구독하는 형태이기 때문에, 위에서 거론된 두가지 문제가 발생하지 않는다는 장점이 있다.</p>
<h1 id="eventbridge의-구성-요소">EventBridge의 구성 요소</h1>
<p><img src="https://images.velog.io/images/techy-yunong/post/8d5171a7-5e4f-4b34-8d24-535840a90e35/image.png" alt=""></p>
<h2 id="event">Event</h2>
<p>이벤트소스에서 부터오는 이벤트라 보면 된다. AWS 서비스를 예를 들면 EC2의 상태가 변경된다던지, Auto Scaling Group에 인스턴스가 추가될 때, event가 발생한다. 만약 내가 개발하고 있는 커머스 어플리케이션이라 하면, 사용자가 특정 상품을 구매한 경우, 상품을 구매했다는 이벤트를 생성할 수 있다.</p>
<h2 id="event-rule">Event Rule</h2>
<p>Event가 발생한 경우, Event Rule과 매칭되는 이벤트만 Event Rule에 연결된 Event Target으로 라우팅 된다. EDA에서 보면 구독을 할 수 있는 대상이라 보면 된다. 만약 Auto Scaling Group의 인스턴스가 추가되어 이벤트가 발생되었다고 하면, 해당 이벤트의 패턴과 매칭되는 Event Rule은 모두 이 이벤트를 통과시킬 것이다. 이 이벤트의 패턴을 Event Pattern이라 부르고, 이 패턴을 만족하는 이벤트는 이벤트 패턴을 가지는 모든 Rule의 Target에 라우팅 된다.</p>
<p>EC2 인스턴스가 종료 되었을 때 다음과 같은 이벤트가 생성된다.</p>
<pre><code class="language-json">{
  &quot;version&quot;: &quot;0&quot;,
  &quot;id&quot;: &quot;6a7e8feb-b491-4cf7-a9f1-bf3703467718&quot;,
  &quot;detail-type&quot;: &quot;EC2 Instance State-change Notification&quot;,
  &quot;source&quot;: &quot;aws.ec2&quot;,
  &quot;account&quot;: &quot;111122223333&quot;,
  &quot;time&quot;: &quot;2017-12-22T18:43:48Z&quot;,
  &quot;region&quot;: &quot;us-west-1&quot;,
  &quot;resources&quot;: [
    &quot;arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0&quot;
  ],
  &quot;detail&quot;: {
    &quot;instance-id&quot;: &quot;i-1234567890abcdef0&quot;,
    &quot;state&quot;: &quot;terminated&quot;
  }
}</code></pre>
<p>만약 Event rule의 이벤트 패턴을 다음과 같이 입력한 경우, 위 이벤트는 이 Event Rule의 필터링을 통과하고, Event Target까지 이벤트가 전달된다.</p>
<pre><code class="language-json">{
  &quot;source&quot;: [&quot;aws.ec2&quot;],
  &quot;detail-type&quot;: [&quot;EC2 Instance State-change Notification&quot;],
  &quot;detail&quot;: {
    &quot;state&quot;: [&quot;terminated&quot;]
  }
}</code></pre>
<p>만약 terminated를 포함한 EC2 status change에 대한 모든 이벤트를 필터링하고 싶다면 다음과 같이 event pattern을 구성하면 된다.</p>
<pre><code class="language-json">{
  &quot;source&quot;: [&quot;aws.ec2&quot;],
  &quot;detail-type&quot;: [&quot;EC2 Instance State-change Notification&quot;],
  &quot;detail&quot;: {
    &quot;state&quot;: [&quot;pending&quot;, &quot;running&quot;, &quot;shutting-down&quot;, &quot;stopped&quot;, &quot;stopping&quot;, &quot;terminated&quot;]
  }
}</code></pre>
<p>다양한 AWS 서비스의 이벤트 패턴 예제는 <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html">이 문서</a>에서 확인 가능하다.</p>
<p>*주의: 처음에는 혼동이 올수도 있는데, 이벤트가 Event Rule을 선택하는 것이 아니다. Event Rule과 연결된 Event Bus를 통과하는 모든 이벤트를 Event Rule이 필터링하여, 매칭되는 이벤트만 Event Target으로 이동시키는 구조임에 유의하자.</p>
<h2 id="event-target">Event Target</h2>
<p>event rule의 필터링을 통과한 이벤트가 도착하는 대상이다. 하나의 event rule에 여러개의 event target 등록이 가능하다. 즉, 해당 event rule의 이벤트 패턴을 만족하는 이벤트를 구독한다는 개념으로 생각하면 된다. <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-targets.html">Event Target으로 등록 가능한 AWS 서비스 또는 대상</a>은 다음과 같다.</p>
<ul>
<li>API destination</li>
<li>API Gateway</li>
<li>AWS Batch job queue</li>
<li>Amazon CloudWatch Logs group</li>
<li>AWS CodeBuild project</li>
<li>AWS CodePipeline</li>
<li>Amazon Elastic Compute Cloud (Amazon EC2) CreateSnapshot API call</li>
<li>Amazon EC2 RebootInstances API call</li>
<li>Amazon EC2 StopInstances API call</li>
<li>Amazon EC2 TerminateInstances API call</li>
<li>Amazon ECS task</li>
<li>Event bus in a different AWS account or AWS Region</li>
<li>Firehose delivery stream (Amazon Kinesis Data Firehose)</li>
<li>Incident Manager response plan</li>
<li>Inspector assessment template (Amazon Kinesis Data Streams)</li>
<li>Kinesis stream (Kinesis Data Streams)</li>
<li>AWS Lambda function</li>
<li>Amazon Redshift cluster (Data API statement execution)</li>
<li>SageMaker Pipeline</li>
<li>Amazon SNS topic</li>
<li>Amazon SQS queue (including a FIFO queue)</li>
<li>Amazon EC2 Systems Manager (SSM) Automation</li>
<li>SSM OpsItem</li>
<li>SSM Run Command</li>
<li>AWS Step Functions state machine</li>
</ul>
<p>예를 들어, 인스턴스가 종료 되었을 때 slack 등에 알림을 보내고 싶을 때, 슬랙의 incoming-hook API를 사용해 lambda function에 슬랙 알림을 구현해 놓고, event target으로 이 lambda function을 등록하면 인스턴스 종료 케이스에 대한 모니터링을 쉽게 할 수 있다.</p>
<h2 id="event-bus">Event Bus</h2>
<p>이벤트를 받는 역할을 한다. Event Rule을 생성할 때, 하나의 Event Bus에 연결을 해야 한다. 이 Event Bus로 들어오는 이벤트만 Event Rule에서 필터링을 하게 된다. AWS 어카운트 마다 기본적으로 생성하는 Event Bus가 있는데, Default Event Bus라 부르고, AWS 서비스로 부터 생성된 이벤트는 모두 이 이벤트 버스로 들어온다.
목적에 따라 커스텀한 이벤트버스를 만들어 사용할 수 있다. 예를 들어 커머스 어플리케이션을 만드는데 Orders 라는 이벤트버스를 만들고, 주문 관련 이벤트 소스를 Orders Event Bus로 연결하면, 관련 이벤트가 발생하면 Orders Event Bus로 이벤트가 전달되고, 이 안에 존재하는 Event Rule이 필터링하여 Event Target으로 라우팅 할 것이다.</p>
<h1 id="cloudwatch-events와-관계는">CloudWatch Events와 관계는?</h1>
<p>EventBridge가 나오기 전에는 CloudWatch Events가 비슷한 역할을 담당하고 있었다. EventBridge에서 사용하는 API는 CloudWatch Events의 API와 동일하다. 즉, 기존의 CloudWatch Event에서 생성한 리소스가 EventBridge에서도 100% 호환된다고 보면 된다. 현재 AWS console에도 CloudWatch Events, EventBridge 메뉴가 모두 존재하는데 한쪽에서 Event Rule을 생성을 하면 다른쪽에서도 조회가 가능하다. </p>
<p>EventBridge가 CloudWatch Events에 비해 우수한 점은 CloudWatch Events는 AWS 서비스가 생성한 이벤트만 사용이 가능한 반면, EventBridge는 AWS 서비스 뿐만 아니라 SaaS나 개인 어플리케이션으로부터 생성된 이벤트도 사용이 가능하단 점이다.  </p>
<p> 그럼 무엇을 사용해야 할까? 개인적으로는 API 상으로는 100% 호환이 되고, EventBridge가 더 넓은 범위의 기능을 제공하므로 AWS EventBridge를 사용하는게 좋을 것 같다는 의견을 내며 글을 마무리 하고자 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[python] timeit 명령어 전격 해부]]></title>
            <link>https://velog.io/@techy-yunong/python-timeit-anatomy</link>
            <guid>https://velog.io/@techy-yunong/python-timeit-anatomy</guid>
            <pubDate>Wed, 23 Jun 2021 12:43:24 GMT</pubDate>
            <description><![CDATA[<p>파이썬 코드의 성능을 시험해보고 싶을 때 간단하게 시도해볼 수 있는 방법 중 하나가 timeit 명령어이다. 하지만 timeit 명령어를 사용할 때 마다 각 옵션이 정확한 어떤 역할을 하는지 매번 혼동이 될 때가 있다. 앞으로는 혼동을 하지 않도록 이번 기회에 정리해보자.</p>
<p>다음 명령어를 기준으로 해부를 시작해보겠다.</p>
<pre><code class="language-bash">$ python -m timeit &#39;&quot;-&quot;.join(str(n) for n in range(100))&#39;
10000 loops, best of 3: 28.6 usec per loop</code></pre>
<p>위 명령어를 실행하게 되면
(1) <code>&quot;-&quot;.join(str(n) for n in range(100))</code> 코드를 10000번 실행시킨다.
(2) <code>10000번 실행한 시간 / 10000</code>을 계산 하여 평균 값을 낸다.
(3) (1) ~ (2)의 과정을 3번 반복한다.
(4) 3개의 평균 값 중에 가장 작은 값을 보여준다. <code>best of 3: 28.6 usec per loop</code></p>
<p>위 과정을 실행하는 python timeit 모듈의 코드는 <a href="https://github.com/python/cpython/blob/557b9a52edc4445cec293f2cc2c268c4f564adcf/Lib/timeit.py#L241">timeit.main</a> 함수에서 확인할 수 있다</p>
<h3 id="코드-실행-회수와-반복수-지정">코드 실행 회수와 반복수 지정</h3>
<p>코드 실행 횟수 10000과 반복수 3은 기본값이다. 이 값을 조정하고 싶다면 다음과 같이 <code>-n</code> (코드 실행 횟수)과 <code>-r</code> (반복횟수) 옵션을 추가하면 된다.</p>
<pre><code class="language-bash">$ python -m timeit -n 1000 -r 5 &#39;&quot;-&quot;.join(str(n) for n in range(100))&#39;
1000 loops, best of 5: 28.3 usec per loop</code></pre>
<h3 id="초기-실행-코드-삽입">초기 실행 코드 삽입</h3>
<p>반복 실행 별 코드 실행 전에 초기에 한번만 실행하고 싶은 코드가 있다면 <code>-s</code> 옵션을 사용하면 된다. 반복 회수(<code>-r</code>)를 5로 지정되었다면 <code>iterations = 100</code> 코드는 총 5번 실행됨에 유의하자. 이 옵션에 넣은 코드는 <a href="https://github.com/python/cpython/blob/557b9a52edc4445cec293f2cc2c268c4f564adcf/Lib/timeit.py#L70">시간 측정 시 포함되지 않는다</a>.</p>
<pre><code class="language-bash">$ python -m timeit -n 1000 -r 5 -s &quot;iterations = 100&quot; &#39;&quot;-&quot;.join(str(n) for n in range(iterations))&#39;
1000 loops, best of 5: 28.1 usec per loop</code></pre>
<h3 id="파일-내-function-실행">파일 내 function 실행</h3>
<p>만약 python 파일 내의 function을 실행하고 싶다면 다음과 같이 하면 된다.</p>
<p>loop.py 라는 파일이 있고, 해당 파일의 코드는 다음과 같이 정의 되어 있다 가정하자.</p>
<pre><code class="language-python">def loop(iterations):
    &quot;-&quot;.join(str(n) for n in range(iterations))</code></pre>
<p>이 파일 내의 loop 함수를 실행하고 싶다면 다음과 같이 실행하면 된다.</p>
<pre><code class="language-bash">$ python -m timeit -n 1000 -r 5 -s &#39;from loop import loop; iterations = 100&#39; &#39;loop(100)&#39;
1000 loops, best of 5: 21.9 usec per loop</code></pre>
<h3 id="그-외-고려사항">그 외 고려사항</h3>
<ul>
<li><code>-v</code> 옵션: 각 반복 횟수의 결과 값을 모두 보여준다.</li>
</ul>
<pre><code class="language-bash">$ python -m timeit -n 1000 -r 5 -v -s &quot;from loop import loop&quot; &#39;loop(100)&#39;
raw times: 0.0276 0.0233 0.0229 0.0227 0.0227
1000 loops, best of 5: 22.7 usec per loop</code></pre>
<ul>
<li>GC 켜기: timeit 명령어는 <a href="https://github.com/python/cpython/blob/557b9a52edc4445cec293f2cc2c268c4f564adcf/Lib/timeit.py#L164">각 반복 실행 시작 전에 gc를 disable 하고, 반복 실행이 끝다면 다시 enable 시키도록 되어 있다</a>. (<code>-r</code>이 5이면 gc를 5번 껐다 켰다 함). 만약 gc를 enable한 상태로 코드를 돌리고 싶다면 <code>-s</code> 옵션에 <code>gc.enable()</code>을 추가해주면 된다. 만약 자신의 코드가 <a href="https://devguide.python.org/garbage_collector/">python의 GC</a>를 많이 사용하는 코드라면 정확한 성능 측정을 위하여 gc를 enable 하는 것도 고려해보자.</li>
</ul>
<pre><code class="language-bash">$ python -m timeit -n 1000 -r 5 -s &#39;from loop import loop; iterations = 100; gc.enable()&#39; &#39;loop(100)&#39;
1000 loops, best of 5: 22.2 usec per loop</code></pre>
<p>이 정도면 <code>timeit</code> 명령어를 사용할 때 찾아보기에 충분한 것 같다. 끝!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[소켓 프로그래밍] listen API의 backlog 무식하게 알아보기 (feat. gunicorn)]]></title>
            <link>https://velog.io/@techy-yunong/socket-programming-listen-API-backlog</link>
            <guid>https://velog.io/@techy-yunong/socket-programming-listen-API-backlog</guid>
            <pubDate>Wed, 12 May 2021 12:15:50 GMT</pubDate>
            <description><![CDATA[<p> 서버를 위한 socket 프로그래밍을 할 때, listen API를 사용해 socket에 binding 된 host와 port로 들어오는 커넥션 요청을 받겠다고 선언할 수 있다. listen API의 첫번째 인자는 <code>socketfd</code>이며, socket의 file descriptor을 의미한다. 두 번째 인자는 <code>backlog</code> 인데, 이번 글에서는 이 backlog 인자가 정확히 어떤 역할을 하는지 알아보자.</p>
<h1 id="정의">정의</h1>
<p>리눅스 메뉴얼의 <a href="https://man7.org/linux/man-pages/man2/listen.2.html">listen API</a>를 보면, backlog 인자에 대해 다음과 같이 정의되어 있다.</p>
<pre><code>The backlog argument defines the maximum length to which the
queue of pending connections for sockfd may grow.</code></pre><p>구글 변역기의 힘을 빌려보자면
<img src="https://images.velog.io/images/techy-yunong/post/4c2ae96b-8c4d-4c39-b966-4398aeb4fdd3/image.png" alt=""></p>
<p>무슨 말인지 모르겠다 (아직도 먼 기계학습...). 마음가는대로 해석해보면</p>
<pre><code>socketfd의 커넥션 중, 허용 가능한 pending된 커넥션 큐의 최대 길이</code></pre><p>정도가 될 것이다.</p>
<p>그러나 정의 만으로도 정확히 이해하기가 힘들었다. 그래서 무식하게 간단한 프로그램을 만들어 실험을 해보기로 결정했다.</p>
<h1 id="에코-서버-클라이언트-프로그램">에코 서버, 클라이언트 프로그램</h1>
<p>필자는 python을 주로 사용해와서, python이 편하기 때문에 python으로 간단한 프로그램을 짜보았다.</p>
<h2 id="에코-서버-프로그램">에코 서버 프로그램</h2>
<p>클라이언트에서 bytes를 보내면, 그 값을 그대로 돌려주는 간단한 서버 프로그램이다</p>
<pre><code class="language-python"># echo_server.py
import socket
import sys


if __name__ == &#39;__main__&#39;:
    host = sys.argv[1]
    port = int(sys.argv[2])
    num_of_backlog = int(sys.argv[3])

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.bind((host, port))
        s.listen(num_of_backlog)
        print(f&#39;[INFO] listens to {host}:{port} with backlog {num_of_backlog}&#39;)
        while True:
            conn, addr = s.accept()
            with conn:
                print(f&#39;[INFO] connected to {addr[0]}:{addr[1]}&#39;)
                while True:
                    data = conn.recv(1024)
                    if not data:
                        break
                    print(f&#39;[INFO] received data: {data.decode(&quot;utf-8&quot;)}&#39;)

                    # echo to client
                    conn.sendall(data)
    finally:
        print(&#39;[INFO] socket closed&#39;)
        s.close()</code></pre>
<ul>
<li>python 3.8을 사용. f string을 사용했기 때문에 python 3.6 이상부터 작동할 것으로 예상</li>
<li>TCP socket을 사용하였다.</li>
<li>쓰레드가 1개이기 때문에, 실제로 메세지를 주고 받을 수 있는 클라이언트는 동시에 1개 뿐이다.</li>
</ul>
<p>실행은 다음과 같이 하면 된다.</p>
<pre><code>python echo_server.py HOST PORT BACKLOG</code></pre><p>Backlog를 테스트 해보기 위해서, <code>BACKLOG</code>를 인자로 넣었다.</p>
<h2 id="에코-클라이언트-프로그램">에코 클라이언트 프로그램</h2>
<p>이 프로그램은 입력값을 키보드로 부터 받아, 서버로 넘겨주는 간단한 클라이언트 프로그램이다.</p>
<pre><code class="language-python"># echo_client.py
import socket
import sys


if __name__ == &#39;__main__&#39;:
    host = sys.argv[1]
    port = int(sys.argv[2])

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect((host, port))
        print(f&#39;[INFO] connected to {host}:{port}&#39;)
        while True:
            # make prompts
            print(&#39;&gt;&gt;&#39;, end=&#39; &#39;)

            # send input data to server
            input_data = input()
            s.sendall(input_data.encode(encoding=&#39;utf-8&#39;))

            # receive data from server
            received_data = s.recv(1024)
            print(f&#39;[INFO] received data: {received_data.decode(&quot;utf-8&quot;)}&#39;)
    finally:
        print(&#39;[INFO] socket closed&#39;)
        s.close()</code></pre>
<ul>
<li>python 3.8을 사용. f string을 사용했기 때문에 python 3.6 이상부터 작동할 것으로 예상</li>
<li>connection timeout 값은 60초이다. 즉, 60초 안에 TCP 커넥션을 맺지 못하면 timeout error가 발생한다.</li>
</ul>
<p>실행은 다음과 같이 하면 된다. </p>
<h1 id="실험">실험</h1>
<ul>
<li>Mac OS Catalina iTerms2 터미널에서 실험하였다.</li>
<li>서버는 한개만 실행한다. 실행 명령어는 <code>python echo_server.py 127.0.0.1 10000 2</code> 로 backlog 값은 2로 주었다.</li>
<li>클라이언트 프로그램은 총 4개를 실행한다. 실행 명령어는 <code>python echo_client.py 127.0.0.1 10000</code> 이다. 각각 1번, 2번, 3번, 4번 클라이언트라 부르자.</li>
</ul>
<p><img src="https://images.velog.io/images/techy-yunong/post/05b33096-debf-4dd6-80f0-b865596a1d99/image.png" alt=""></p>
<p>상기 이미지를 보면, 4개의 클라이언트 프로그램을 실행했고, 실행 순서는 위에서 부터 차례로 실행했다. 3개까지는 커넥션이 잘 맺어졌고, 값을 입력할 수 있는 promps로 넘어간걸 볼 수 있다. </p>
<p>netstat 명령어를 사용해, 실제 연결된 커넥션을 봐보자. <code>netstat -anlt | grep 10000</code> 명령어를 치면 결과가 다음과 같이 나온다.</p>
<pre><code>tcp4       0      0  127.0.0.1.60240        127.0.0.1.10000        SYN_SENT
tcp4       0      0  127.0.0.1.10000        127.0.0.1.60237        ESTABLISHED
tcp4       0      0  127.0.0.1.60237        127.0.0.1.10000        ESTABLISHED
tcp4       0      0  127.0.0.1.10000        127.0.0.1.60236        ESTABLISHED
tcp4       0      0  127.0.0.1.60236        127.0.0.1.10000        ESTABLISHED
tcp4       0      0  127.0.0.1.10000        127.0.0.1.60235        ESTABLISHED
tcp4       0      0  127.0.0.1.60235        127.0.0.1.10000        ESTABLISHED
tcp4       0      0  127.0.0.1.10000        *.*                    LISTEN</code></pre><p>3개의 클라이언트는 TCP connection이 <code>ESTABLISHED</code> 된 상태이고, 하나의 클라이언트는 <code>SYN_SENT</code> 상태로 대기 중이다. 이 클라이언트가 4번 클라이언트인 것을 추측할 수 있다. 60초가 지난 후, 4번 클라이언트는 TimeoutError와 함께 종료된다. (4번 클라이언트는 이미지 캡처에서 아웃 시키겠다.)</p>
<pre><code>[INFO] socket closed
Traceback (most recent call last):
  File &quot;echo_client.py&quot;, line 11, in &lt;module&gt;
    s.connect((host, port))
TimeoutError: [Errno 60] Operation timed out</code></pre><p><code>ESTABLISHED</code> 인 1번, 2번, 3번의 클라이언트에 입력을 해보면, 실제로 작동하는 클라이언트는 1번 클라이언트고, 2번, 3번 클라이언트는 아무런 응답이 없다. 서버의 스레드가 하나여서, 실제로 서버 프로그램과 데이터를 주고 받을 수 있는 클라이언트는 1개인 것이다. 즉, 2번, 3번의 연결 상태는 <code>ESTABLISHED</code> 이미지만, listen 정의에서 언급된 큐에 대기중인 상태인 것을 알 수 있다.</p>
<p><img src="https://images.velog.io/images/techy-yunong/post/209569c7-70e6-4de1-9fc1-bd2ed4fa47d7/image.png" alt=""></p>
<p>1번 클라이언트를 강제 종료해보자 (Ctrl+C를 주어, KeyboardInterrupt를 발생시켰다). 대기하고 있던 2번 클라이언트가 서버로 부터 응답을 받게 된다.  </p>
<p><img src="https://images.velog.io/images/techy-yunong/post/4c99d187-093f-4f7c-8153-e2fc42c9dacc/image.png" alt=""></p>
<p>이를 통해 backlog 정의에서 언급한 queue는 FIFO인것을 확인할 수 있다. (큐가 맞다!)</p>
<h1 id="정리">정리</h1>
<p> 만약 서버 프로그램이 동시에 응답할 수 있는 수가 3개라 가정하고, backlog 값을 5라 가정하자. 10개의 요청이 차례로 들어왔을 때, 상황을 그림을 그려보면 다음과 같다. (숫자는 요청을 의미하고, 가장 먼저 들어온 요청이 1번이다)</p>
<p><img src="https://images.velog.io/images/techy-yunong/post/5a28fd34-63a6-48bd-99a8-a6d25d856ad0/image.png" alt=""></p>
<ul>
<li><p>1~3 요청을 보낸 클라이언트는 TCP connection이 established되고, 서버와 데이터를 주고 받을 수 있을 것이다. </p>
</li>
<li><p>listen backlog queue에 있는 4<del>8번 요청은 TCP connection은 established 되었지만, 서버와 데이터를 주고 받지는 못한다. 1</del>3번 요청 중 커넥션이 끊어지면, 4번 부터 차례로 데이터를 주고 받을 수 있도록 backlog에서 큐를 이동할 것이다.</p>
</li>
<li><p>tcp max sync backlog라는 곳에 저장된 9, 10번 요청은 <code>SYNC_SENT</code> 상태를 유지하게 된다. 실제로는 유지한다기 보다는 클라이언트에서 지속적으로 <code>SYNC</code> 요청을 시도하고, 서버 쪽에서는 계속 패킷을 drop 시킨다. tcp connection timeout이 값 이상으로 계속 시도하게되면, 클라이언트 쪽에서 커넥션에 대한 timeout을 발생키시고, 연결 시도를 중단한다.</p>
</li>
<li><p>이 backlog는 OS 종류에 따라 없을 수도 있고, 다르게 동작할 수도 있다. 예를 들어 어떤 OS에서는 이 backlog가 존재하지 않고 바로 요청을 refuse 할수도 있다. 이 내용은 <a href="https://man7.org/linux/man-pages/man2/listen.2.html">listen 매뉴얼</a>에도 잘 나와있다.</p>
</li>
</ul>
<ul>
<li>tcp max sync backlog를 큐라 부르지 않는 이유는 순서가 보장이 안되기 때문이다. 실험해본 결과, 이 backlog에 대기중인 상태에서 listen의 backlog queue로 넘어갈 때, 요청한 순서대로 listen의 backlog 큐로 넘어가지 않았다. 단, mac OS에서 실험해보았기 때문에, mac OS에 국한된 작동일 수도 있다.</li>
</ul>
<h1 id="주의">주의</h1>
<ul>
<li>linux 기준으로 listen의 backlog 값이 <code>net.core.somaxconn</code> 커널 파라미터 값보다 크다면, 실제 적용은 <code>net.core.somaxconn</code> 값이 된다.</li>
<li>listen의 backlog 인자값과 실제 backlog queue 사이즈는 OS에 따라 다를 수도 있다. backlog 인자값이 1로 지정하더라도, OS에 따라 실제 queue의 사이즈는 4로 적용될 수도 있음을 유의하자.</li>
</ul>
<h1 id="feat-gunicorn">feat. gunicorn</h1>
<p><a href="https://gunicorn.org/">gunicorn</a>은 python의 WSGI 인터페이스를 지원하는 unix를 위한 http 서버이다. 설정값중에 <code>backlog</code>라는 값이 있다.  gunicorn의 코드를 보았을 때, 이 <code>backlog</code> 값은 실제 listen API의 backlog 값과 동일한 것을 알 수 있었다. 다음 코드는 gunicorn 20.1.0의 <code>gunicorn/sock.py</code> 파일의 내용이다.</p>
<p><img src="https://images.velog.io/images/techy-yunong/post/78cc1e02-9b31-41b8-ae48-a87ce86078b5/image.png" alt=""></p>
<p>그렇다. gunicorn의 backlog 설정은, 말그대로 listen API의 backlog 인자와 동일하다.</p>
<h1 id="마무리">마무리</h1>
<p>사실 이 블로그는 gunicorn의 backlog 값이 어떤 역할을 하는지 궁금해서 시작되었다. 처음에는 gunicorn을 가지고 실험을 해보다, 결국 gunicorn의 코드를 까보게 되었고, listen API를 사용하는 것을 알고, listen API도 무식하게 backlog 인자를 테스트해보면서 여기까지 오게 되었다. 여러 OS 환경(특히 Window)에서 테스트하지 못했다는 점이 아쉽지만, 이 정도 정보면 실무에서 실제로 값을 설정할 때, 내가 뭔짓을 하고 있는지 아는데 부족하지 않을 것이다.</p>
<h1 id="참고-자료">참고 자료</h1>
<ul>
<li><a href="https://man7.org/linux/man-pages/man2/listen.2.html">https://man7.org/linux/man-pages/man2/listen.2.html</a></li>
<li><a href="https://meetup.toast.com/posts/55">https://meetup.toast.com/posts/55</a></li>
<li><a href="https://docs.python.org/3/library/socket.html">https://docs.python.org/3/library/socket.html</a></li>
<li><a href="https://realpython.com/python-sockets/">https://realpython.com/python-sockets/</a></li>
<li><a href="https://stackoverflow.com/questions/62641621/what-is-the-difference-between-tcp-max-syn-backlog-and-somaxconn">https://stackoverflow.com/questions/62641621/what-is-the-difference-between-tcp-max-syn-backlog-and-somaxconn</a></li>
<li><a href="https://stackoverflow.com/questions/36594400/what-is-backlog-in-tcp-connections">https://stackoverflow.com/questions/36594400/what-is-backlog-in-tcp-connections</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>