<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>collin-jeong.log</title>
        <link>https://velog.io/</link>
        <description>세상에 선한 영향력을 끼치는 서비스를 개발하고 싶은 개발자입니다</description>
        <lastBuildDate>Thu, 24 Apr 2025 14:57:55 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>collin-jeong.log</title>
            <url>https://velog.velcdn.com/images/collin-jeong/profile/6ea2a7cd-062e-468e-8cac-fe319570516f/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. collin-jeong.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/collin-jeong" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[AWS SageMaker 도입 & scale down to zero 적용]]></title>
            <link>https://velog.io/@collin-jeong/AWS-SageMaker-%EB%8F%84%EC%9E%85-scale-down-to-zero-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@collin-jeong/AWS-SageMaker-%EB%8F%84%EC%9E%85-scale-down-to-zero-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Thu, 24 Apr 2025 14:57:55 GMT</pubDate>
            <description><![CDATA[<h2 id="sagemaker-도입-이유">sagemaker 도입 이유</h2>
<p>현재 사내에서 개발 중인 의사결정 최적화 AI 플랫폼에서 
비용을 고려하려 추론 서버를 격리된 환경에 제공하는 방법을 리서치하다 발견했습니다 </p>
<p>sagemaker의 여러 기능 중 inference 기능을 사용할 예정입니다
inference도 아래의 타입이 존재합니다 </p>
<ul>
<li>Real-Time<ul>
<li>상시 운영되는 엔드포인트를 통해 실시간 요청을 처리합니다</li>
</ul>
</li>
<li>Serverless<ul>
<li>Lambda처럼 자동으로 인스턴스를 스케일 업/다운합니다</li>
<li>사용량을 기반으로 과금됩니다</li>
</ul>
</li>
<li>asynchronous<ul>
<li>비동기 방식으로 요청을 큐에 넣고, 결과를 나중에 받아보는 방식입니다</li>
<li>모델 예측 시간이 길거나 즉시 결과가 필요 없는 경우 적합합니다</li>
</ul>
</li>
</ul>
<blockquote>
<p>이 중에서도 특히 Serverless Inference 방식이
예상 트래픽이 불규칙하고, 사용량 기반으로 비용을 추적할 수 있다는 점에서
적합한 인퍼런스 구조라고 판단해서 POC를 진행했습니다</p>
</blockquote>
<br/>



<h2 id="sagemaker-구현">sagemaker 구현</h2>
<h3 id="1-인퍼런스-도커-이미지-만들기">1. 인퍼런스 도커 이미지 만들기!</h3>
<p>의사결정 최적화 AI 플랫폼은 <a href="https://docs.aws.amazon.com/sagemaker/latest/dg/neo-deployment-hosting-services-container-images.html">sagemaker에서 제공해주는 모델</a>을 사용한 게 아니라 
custom한 이미지를 사용함으로 /ping /invocation API를 구현하여야 합니다
<a href="https://docs.aws.amazon.com/sagemaker/latest/dg/adapt-inference-container.html">관련 docs</a></p>
<p>만약 sagemaker에서 제공해주는 모델을 사용하신다면 /ping /invocation API를 구현할 필요없이 해당 함수들을 정해진 파일에 구현해놓으시면 sagemaker가
알아서 샥 잘 호출해서 inference를 동작시켜줍니다!</p>
<ul>
<li>PyTorch 및 MXNet 모델 : model_fn, input_fn, predict_fn, output_fn</li>
<li>inf1 인스턴스 또는 onnx, xgboost, keras 컨테이너 이미지의 경우 : neo_preprocess, neo_postprocess</li>
<li>TensorFlow 모델의 경우 : input_handler(inference.py 안에), output_handler (inference.py 안에)</li>
</ul>
<p><a href="https://docs.aws.amazon.com/ko_kr/sagemaker/latest/dg/neo-deployment-hosting-services-prerequisites.html">관련 docs</a></p>
<h3 id="2-sagemaker-endpoint-배포">2. sagemaker endpoint 배포</h3>
<p>sagemaker model, sagemaker endpoint config, sagemaker endpoint를 연결해서 생성  </p>
<p>serverless로 배포해야 하기에 sagemaker endpoint config를 
아래와 같이 생성</p>
<pre><code>response = client.create_endpoint_config(
   EndpointConfigName=&quot;&lt;your-endpoint-configuration&gt;&quot;,
   ProductionVariants=[
        {
            &quot;ModelName&quot;: &quot;&lt;your-model-name&gt;&quot;,
            &quot;VariantName&quot;: &quot;AllTraffic&quot;,
            &quot;ServerlessConfig&quot;: {
                &quot;MemorySizeInMB&quot;: 2048,
                &quot;MaxConcurrency&quot;: 20,
                &quot;ProvisionedConcurrency&quot;: 10,
            }
        } 
    ]
)</code></pre><p><a href="https://docs.aws.amazon.com/sagemaker/latest/dg/serverless-endpoints-create-config.html">관련 docs</a></p>
<h3 id="❗️트러블-슈팅">❗️트러블 슈팅</h3>
<ul>
<li>ray init할 때 에러가 발생<ul>
<li>lambda는 /tmp 에서만 쓰기 권한이 있는데, ray가 병렬처리를 위해 이외의 경로에서 쓰기 작업을 시도하면서 발생하는 것 같다는 ML개발자 분의 답변을 듣고
비용을 고려 해야하는 요구사항을 만족 못할 난관에 봉착했습니다 </li>
</ul>
</li>
</ul>
<h2 id="scale-down-to-zero">scale down to zero</h2>
<p>그러다 발견한 scale down to zero!!
3개의 sagemaker inference type 중 real-time을 auto-scaling 하는 기능을 활용해서 설정한 기간동안 트래픽이 발생하지 않으면 아예 인스턴스를 0개로 내려버리는 기능입니다</p>
<p><a href="https://aws.amazon.com/ko/blogs/machine-learning/unlock-cost-savings-with-the-new-scale-down-to-zero-feature-in-amazon-sagemaker-inference/">scale down to zero 관련 docs</a></p>
<h3 id="endpoint-수정">endpoint 수정</h3>
<ol>
<li>endpoint config 수정
 <code>MinInstanceCount</code>를 0로 설정함으로 zero instance를 허용함<pre><code>sagemaker_client.create_endpoint_config(
 EndpointConfigName=endpoint_config_name,
 ExecutionRoleArn=role,
 ProductionVariants=[
     {
         &quot;VariantName&quot;: variant_name,
         &quot;InstanceType&quot;: instance_type,
         &quot;InitialInstanceCount&quot;: 1,
         &quot;ModelDataDownloadTimeoutInSeconds&quot;: model_data_download_timeout_in_seconds,
         &quot;ContainerStartupHealthCheckTimeoutInSeconds&quot;: container_startup_health_check_timeout_in_seconds,
         &quot;ManagedInstanceScaling&quot;: {
             &quot;Status&quot;: &quot;ENABLED&quot;,
             &quot;MinInstanceCount&quot;: 0,
             &quot;MaxInstanceCount&quot;: max_instance_count,
         },
         &quot;RoutingConfig&quot;: {&quot;RoutingStrategy&quot;: &quot;LEAST_OUTSTANDING_REQUESTS&quot;},
     }
 ],
)</code></pre></li>
<li>endpoint 생성<pre><code>sagemaker_client.create_endpoint(
 EndpointName=endpoint_name,
 EndpointConfigName=endpoint_config_name,
)
</code></pre></li>
</ol>
<pre><code>
3. inference component 생성</code></pre><p>sagemaker_client.create_inference_component(
    InferenceComponentName=inference_component_name,
    EndpointName=endpoint_name,
    VariantName=variant_name,
    Specification={
        &quot;ModelName&quot;: model_name,
        &quot;StartupParameters&quot;: {
            &quot;ModelDataDownloadTimeoutInSeconds&quot;: 3600,
            &quot;ContainerStartupHealthCheckTimeoutInSeconds&quot;: 3600,
        },
        &quot;ComputeResourceRequirements&quot;: {
            &quot;MinMemoryRequiredInMb&quot;: 1024,
            &quot;NumberOfAcceleratorDevicesRequired&quot;: 1,
        },
    },
    RuntimeConfig={
        &quot;CopyCount&quot;: 1,
    },
)sagemaker_client.create_inference_component(
    InferenceComponentName=inference_component_name,
    EndpointName=endpoint_name,
    VariantName=variant_name,
    Specification={
        &quot;ModelName&quot;: model_name,
        &quot;StartupParameters&quot;: {
            &quot;ModelDataDownloadTimeoutInSeconds&quot;: 3600,
            &quot;ContainerStartupHealthCheckTimeoutInSeconds&quot;: 3600,
        },
        &quot;ComputeResourceRequirements&quot;: {
            &quot;MinMemoryRequiredInMb&quot;: 1024,
            &quot;NumberOfAcceleratorDevicesRequired&quot;: 1,
        },
    },
    RuntimeConfig={
        &quot;CopyCount&quot;: 1,
    },
)</p>
<pre><code>
&gt; 기존 설정과 다른 점 : endpoint config 와 model 연결을 끊었습니다
  - as-is
      - model - endpoint config - endpoint 로 연결
    ![](https://velog.velcdn.com/images/collin-jeong/post/06921696-94b5-4b45-b730-d94ae02e285f/image.png)
  - to-be
      ![](https://velog.velcdn.com/images/collin-jeong/post/1a73edf9-e4cf-4f20-87d7-a05ad50c2778/image.png)


[inference component 관련 docs](https://aws.amazon.com/ko/blogs/machine-learning/reduce-model-deployment-costs-by-50-on-average-using-sagemakers-latest-features/)

### scaling policies 추가
scale down to zero를 위해서 아래 2개의 scaling policy를 설정해야 합니다
- inference component model copy를 0개까지 scale down하는 `target tracking` 정책
- inference component model copy를 0개에서 scale up하는 `step scaling` 정책

1. `target tracking` 정책

`MinCapacity` 를 0으로 설정하여 scale down to zero 가능합니다</code></pre><p>aas_client.register_scalable_target(
    ServiceNamespace=service_namespace,
    ResourceId=resource_id,
    ScalableDimension=scalable_dimension,
    MinCapacity=0,
    MaxCapacity=max_copy_count,  # Replace with your desired maximum number of model copies
)</p>
<pre><code>
`TargetValue` 는 동시 요청수가 해당 값 이상이면 capacity를 늘립니다</code></pre><p>aas_client.put_scaling_policy(
    PolicyName=&quot;inference-component-target-tracking-scaling-policy&quot;,
    PolicyType=&quot;TargetTrackingScaling&quot;,
    ServiceNamespace=service_namespace,
    ResourceId=resource_id,
    ScalableDimension=scalable_dimension,
    TargetTrackingScalingPolicyConfiguration={
        &quot;PredefinedMetricSpecification&quot;: {
            &quot;PredefinedMetricType&quot;: &quot;SageMakerInferenceComponentConcurrentRequestsPerCopyHighResolution&quot;,
        },
        # Low TPS + load TPS
        &quot;TargetValue&quot;: 5,  # you need to adjust this value based on your use case
        &quot;ScaleInCooldown&quot;: 300,  # default
        &quot;ScaleOutCooldown&quot;: 300,  # default
    },
)</p>
<pre><code>&gt; scaling이 되는 동작 원리
    - cloudWatch 알람 기반


2. `step scaling` 정책
NoCapacityInvocationFailures 트리거되는 CloudWatch alarm을 생성하고
alarm이 트리거되어 `step scaling` 정책이 실행됩니다

그래서 `NoCapacityInvocationFailures`이면 `ScalingAdjustment`을 1로 설정하여 model copy를 0에서 1로 설정하여 scale up 됩니다</code></pre><p>aas_client.put_scaling_policy(
    PolicyName=&quot;inference-component-step-scaling-policy&quot;,
    PolicyType=&quot;StepScaling&quot;,
    ServiceNamespace=service_namespace,
    ResourceId=resource_id,
    ScalableDimension=scalable_dimension,
    StepScalingPolicyConfiguration={
        &quot;AdjustmentType&quot;: &quot;ChangeInCapacity&quot;,
        &quot;MetricAggregationType&quot;: &quot;Maximum&quot;,
        &quot;Cooldown&quot;: 60,
        &quot;StepAdjustments&quot;:
          [
             {
               &quot;MetricIntervalLowerBound&quot;: 0,
               &quot;ScalingAdjustment&quot;: 1 # you need to adjust this value based on your use case
             }
          ]
    },
)</p>
<pre><code>
</code></pre><p>cw_client.put_metric_alarm(
    AlarmName=&#39;ic-step-scaling-policy-alarm&#39;,
    AlarmActions=<step_scaling_policy_arn>,  # Replace with your actual ARN
    MetricName=&#39;NoCapacityInvocationFailures&#39;,
    Namespace=&#39;AWS/SageMaker&#39;,
    Statistic=&#39;Maximum&#39;,
    Dimensions=[
        {
            &#39;Name&#39;: &#39;InferenceComponentName&#39;,
            &#39;Value&#39;: inference_component_name  # Replace with actual InferenceComponentName
        }
    ],
    Period=30,
    EvaluationPeriods=1,
    DatapointsToAlarm=1,
    Threshold=1,
    ComparisonOperator=&#39;GreaterThanOrEqualToThreshold&#39;,
    TreatMissingData=&#39;missing&#39;
)</p>
<p>```</p>
<h2 id="성과-및-보안점">성과 및 보안점</h2>
<p>사용량 기반 과금과 함께 격리된 추론 서버환경을 제공하는 방법은 찾았지만
serverless의 cold start 관련된 문제가 존재합니다
실제 테스트 결과는 cold start시 3~6분입니다</p>
<p>다만 합리적인 요금이 우선순위가 높아서 해당 이슈는 격리된 추론 서버를 사용할
유저가 로그인하면 미리 요청을 보내 warm하게 만드는 방식으로 보안할 예정입니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[mongo db oplog 뭐냐... 넌?]]></title>
            <link>https://velog.io/@collin-jeong/mongo-db-oplog-%EB%AD%90%EB%83%90...-%EB%84%8C</link>
            <guid>https://velog.io/@collin-jeong/mongo-db-oplog-%EB%AD%90%EB%83%90...-%EB%84%8C</guid>
            <pubDate>Thu, 10 Apr 2025 15:26:36 GMT</pubDate>
            <description><![CDATA[<h2 id="글을-적게된-이유">글을 적게된 이유</h2>
<p>사내에서 시스템 리팩토링을 통해서 mongodb에 저장해야할 데이터를 80% 이상 개선한 경험이 있는데 이때 mongodb의 디스크 크기가 줄지 않는 트러블 슈팅을 공유하려고 합니다</p>
<h2 id="배경">배경</h2>
<p>기존 레거시 시스템은 AI를 학습시켰던 데이터에 대한 미리보기를 위해서 원본 csv는 s3에 미리보기를 위해 csv의 row들을 mongodb에 저장하고 있었는데 s3의 select object content 기능을 통해서 s3에 저장된 원본 csv를 읽도록 리팩토링하면서 mongodb의 파싱된 데이터는 더 이상 사용되지 않아서 해당 데이터를 삭제하게 되었는데 mongodb에 할당된 디스크 크기가 줄지 않는 현상이 발생했습니다</p>
<h2 id="한-번-원인-파악해보자">한 번 원인 파악해보자</h2>
<p>아래 명령어로 현재 어떤 데이터 상태가 어떤지 파악했습니다</p>
<pre><code class="language-bash">db.adminCommand( { listDatabases: 1 } )
db.collection.stats()</code></pre>
<p>결과는 실제 저장된 데이터의 크기는 약 10GB인데 
할당된 디스크 공간은 약 52GB로 시스템을 개선하기 전 데이터 크기로 할당되어 있었습니다</p>
<h2 id="첫번째-최적화">첫번째 최적화</h2>
<p>mongodb PM분의 <a href="https://alexbevi.com/blog/2020/03/15/identifying-and-reclaiming-disk-space-in-mongodb/">포스팅</a>을 확인해보니 mongodb는 문서를 삭제할 때 데이터 파일에 빈 레코드 목록을 유지한다고 합니다
그래서 삭제된 이후 할당된 디스크의 크기를 최적화하기 위해서는 <code>compact</code> 명령어를 사용해서 최적화를 진행한다고 합니다</p>
<p>최적화를 완료하고 디스크 용량과 사용량을 고려해서 클러스터를 내린 뒤에
보다 더 최적화할 게 없는지 리서치하게 되었습니다</p>
<h2 id="드디어-나왔다-oplog">드디어 나왔다 oplog</h2>
<p>리서치를 하다가 oplog라는 키워드를 발견했고 공식 docs를 찾아보게 됐습니다
mongodb 클러스터의 복제본 관리 원리에 대해서 간단하게 설명하겠습니다
mongodb 클러스터에서 primary, secondary node 중 primary node에서 모든 쓰기 작업을 수신하고 데이터 변경 사항을 <code>oplog</code>에 기록하면 secondary node에서 oplog를 복제한 뒤 적용하는 방식으로 복제본을 관리합니다
<a href="https://www.mongodb.com/ko-kr/docs/manual/replication/">https://www.mongodb.com/ko-kr/docs/manual/replication/</a></p>
<h2 id="두번째-최적화">두번째 최적화</h2>
<p>저는 클러스터, 디스크 최대 용량도 downgrade 하였는데<br>downgrade하기 전의 기본 oplog의 크기로 설정되어 있었습니다
(기본 oplog 크기 : 디스크 여유 공간의 5%) </p>
<p>그래서 <a href="https://www.mongodb.com/ko-kr/docs/manual/tutorial/change-oplog-size/">복제본 세트 멤버의 oplog 크기 변경 문서</a>를 참고해서 해결했습니다</p>
<p>아래와 같이 oplog의 크기를 조정하고</p>
<pre><code>db.adminCommand({replSetResizeOplog: 1, size: Double(16000)})</code></pre><p>oplog의 크기를 조정했지만 mongodb는 문서를 삭제할 때 데이터 파일에 빈 레코드 목록을 유지함으로 아래 명령어로 할당된 디스크를 최적화 합니다</p>
<pre><code>db.runCommand({ &quot;compact&quot; : &quot;oplog.rs&quot; } )</code></pre><p><a href="https://www.mongodb.com/ko-kr/docs/manual/core/replica-set-oplog/">https://www.mongodb.com/ko-kr/docs/manual/core/replica-set-oplog/</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB 마이그레이션 전략 변경]]></title>
            <link>https://velog.io/@collin-jeong/DB-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%EC%A0%84%EB%9E%B5-%EB%B3%80%EA%B2%BD</link>
            <guid>https://velog.io/@collin-jeong/DB-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%EC%A0%84%EB%9E%B5-%EB%B3%80%EA%B2%BD</guid>
            <pubDate>Tue, 15 Nov 2022 16:19:40 GMT</pubDate>
            <description><![CDATA[<h2 id="변경전-typeorm-sync">[변경전] typeorm sync</h2>
<br/>


<ul>
<li><strong>단점</strong></li>
</ul>
<ol>
<li><p>현재 백앤드의 entity따라서 db와 동기화되는 typeorm의 sync 옵션을 사용했는데 
해당 기능의 문제는 db 컬럼명 수정을 하더라도 변경이 아닌 삭제후 생성으로 기존의 데이터가 유실됨</p>
</li>
<li><p>데이터가 존재하는 경우 db 마이그레이션시 db error 발생 ⇒ 관련 데이터 다 지우고 스키마 마이그레이션 진행 </p>
</li>
</ol>
<br/> 

<ul>
<li><strong>장점</strong></li>
</ul>
<ol>
<li>빠른 개발 속도 <br/>

</li>
</ol>
<p>💡 &nbsp;&nbsp;<strong>운영(라이브/프로덕션)환경 전까지는 데이터 유실보다 속도가 중요해 잘 사용했지만</strong></p>
<p>&nbsp;　　<strong>운영환경으로 올리게 됨으로써 전략 수정이 필요</strong> </p>
<br/> 

<hr>
<h2 id="변경후-typeorm-migration-cli">[변경후] typeorm migration cli</h2>
<br/> 

<ul>
<li><strong>단점</strong></li>
</ul>
<ol>
<li><p>개발추가 공수 (migration 파일 관리를 철저히 해야 한다) </p>
</li>
<li><p>여전히 데이터가 존재하는 경우 db 마이그레이션시 뻗음</p>
<br/></li>
</ol>
<ul>
<li><strong>장점</strong></li>
</ul>
<ol>
<li>해당 기능은 컬럼명 변경시 의도한대로 컬럼명만 변경된다 </li>
</ol>
<br/> 

<ul>
<li><strong>보완사항</strong></li>
</ul>
<p>&nbsp;　　단점의 1번은 안전성과 트레이드 오프 관계라 어쩔 수 없지만 2번은 필수적으로 해결해야 할 부분</p>
<p>&nbsp;　　[단점2의 보완사항] - 생성된 마이그레이션 코드를 직접 수정</p>
<br/>

<p><strong># CASE) db 구조 변경으로 fk 관련한 수정 사항 발생</strong><br><br/></p>
<pre><code>grand, parent, child 테이블 존재

grand - parent = 1:N
parent - child = 1:1 과 같은 관계에서

grand - parent = 1:N
grand - child = 1:1 과 같은 관계로 db 구조 변경

즉 child의 부모 테이블이 parent -&gt; grand 로 변경되는 경우
</code></pre><p>[수정전]  db 스키마 마이그레이션 코드만 존재</p>
<pre><code class="language-jsx">import { MigrationInterface, QueryRunner } from &quot;typeorm&quot;;

export class fkTest1667979162145 implements MigrationInterface {
    name = &#39;fkTest1667979162145&#39;

    public async up(queryRunner: QueryRunner): Promise&lt;void&gt; {
        await queryRunner.query(`ALTER TABLE \`child\` DROP FOREIGN KEY \`FK_1e32750d2d74bb369f3894c379b\``);
        await queryRunner.query(`DROP INDEX \`REL_1e32750d2d74bb369f3894c379\` ON \`child\``);
        await queryRunner.query(`ALTER TABLE \`child\` CHANGE \`parent_id\` \`grand_id\` int NOT NULL`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD UNIQUE INDEX \`IDX_ca31559c5ebea54d722bdbdc92\` (\`grand_id\`)`);
        await queryRunner.query(`CREATE UNIQUE INDEX \`REL_ca31559c5ebea54d722bdbdc92\` ON \`child\` (\`grand_id\`)`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD CONSTRAINT \`FK_ca31559c5ebea54d722bdbdc927\` FOREIGN KEY (\`grand_id\`) REFERENCES \`grand\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`);
    }

    public async down(queryRunner: QueryRunner): Promise&lt;void&gt; {
        await queryRunner.query(`ALTER TABLE \`child\` DROP FOREIGN KEY \`FK_ca31559c5ebea54d722bdbdc927\``);
        await queryRunner.query(`DROP INDEX \`REL_ca31559c5ebea54d722bdbdc92\` ON \`child\``);
        await queryRunner.query(`ALTER TABLE \`child\` DROP INDEX \`IDX_ca31559c5ebea54d722bdbdc92\``);
        await queryRunner.query(`ALTER TABLE \`child\` CHANGE \`grand_id\` \`parent_id\` int NOT NULL`);
        await queryRunner.query(`CREATE UNIQUE INDEX \`REL_1e32750d2d74bb369f3894c379\` ON \`child\` (\`parent_id\`)`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD CONSTRAINT \`FK_1e32750d2d74bb369f3894c379b\` FOREIGN KEY (\`parent_id\`) REFERENCES \`parent\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`);
    }
}</code></pre>
<p>[수정 후] FK, Index 와 같은 제약사항이 삭제된 이후 데이터 마이그레이션 코드를 삽입</p>
<pre><code class="language-jsx">import { MigrationInterface, QueryRunner } from &quot;typeorm&quot;;

export class fkTest1667979162145 implements MigrationInterface {
    name = &#39;fkTest1667979162145&#39;

    private async holdNewData(queryRunner: QueryRunner){
        const result = await queryRunner.query(
            `SELECT 
                child.id, 
                child.parent_id, 
                parent.grand_id 
            FROM 
                child
            JOIN
                parent
            ON 
                parent.id = child.parent_id`
        );
        console.log(result)
        return result;
    }

    private async saveNewData(queryRunner: QueryRunner, newData){
        await Promise.all(newData.map(async nd =&gt; await queryRunner.query(
            `UPDATE child SET grand_id = ${nd.grand_id} WHERE id = ${nd.id}`
        )));
    }

    public async up(queryRunner: QueryRunner): Promise&lt;void&gt; {
        const newData = await this.holdNewData(queryRunner);
        await queryRunner.query(`ALTER TABLE \`child\` DROP FOREIGN KEY \`FK_1e32750d2d74bb369f3894c379b\``);
        await queryRunner.query(`DROP INDEX \`REL_1e32750d2d74bb369f3894c379\` ON \`child\``);
        await this.saveNewData(queryRunner, newData);
        await queryRunner.query(`ALTER TABLE \`child\` CHANGE \`parent_id\` \`grand_id\` int NOT NULL`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD UNIQUE INDEX \`IDX_ca31559c5ebea54d722bdbdc92\` (\`grand_id\`)`);
        await queryRunner.query(`CREATE UNIQUE INDEX \`REL_ca31559c5ebea54d722bdbdc92\` ON \`child\` (\`grand_id\`)`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD CONSTRAINT \`FK_ca31559c5ebea54d722bdbdc927\` FOREIGN KEY (\`grand_id\`) REFERENCES \`grand\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`);
    }


    public async holdPrevData(queryRunner: QueryRunner){
            //up에서 동작하는 함수와 반대 기능
    }
    public async savePrevData(queryRunner: QueryRunner, newData){
            //up에서 동작하는 함수와 반대 기능
    }
    public async down(queryRunner: QueryRunner): Promise&lt;void&gt; {
        const newData = await this.holdPrevData(queryRunner);
        await queryRunner.query(`ALTER TABLE \`child\` DROP FOREIGN KEY \`FK_ca31559c5ebea54d722bdbdc927\``);
        await queryRunner.query(`DROP INDEX \`REL_ca31559c5ebea54d722bdbdc92\` ON \`child\``);
        await this.savePrevData(queryRunner, newData);
        await queryRunner.query(`ALTER TABLE \`child\` DROP INDEX \`IDX_ca31559c5ebea54d722bdbdc92\``);
        await queryRunner.query(`ALTER TABLE \`child\` CHANGE \`grand_id\` \`parent_id\` int NOT NULL`);
        await queryRunner.query(`CREATE UNIQUE INDEX \`REL_1e32750d2d74bb369f3894c379\` ON \`child\` (\`parent_id\`)`);
        await queryRunner.query(`ALTER TABLE \`child\` ADD CONSTRAINT \`FK_1e32750d2d74bb369f3894c379b\` FOREIGN KEY (\`parent_id\`) REFERENCES \`parent\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`);

    }

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[업무방식(애자일) & 대시보드]]></title>
            <link>https://velog.io/@collin-jeong/%EC%97%85%EB%AC%B4%EB%B0%A9%EC%8B%9D%EC%95%A0%EC%9E%90%EC%9D%BC-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C</link>
            <guid>https://velog.io/@collin-jeong/%EC%97%85%EB%AC%B4%EB%B0%A9%EC%8B%9D%EC%95%A0%EC%9E%90%EC%9D%BC-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C</guid>
            <pubDate>Tue, 15 Nov 2022 15:50:15 GMT</pubDate>
            <description><![CDATA[<p>IT 씬에서 많이 애자일, 애자일 노래를 부르고 다닙니다 근데 과연 애자일은 뭘 의미하는 걸까요…?? </p>
<p>잠깐 딱딱한 이야기를 하면 <strong>애자일</strong>의 사전적 의미는 <strong>민첩함</strong> 이고 </p>
<p>소프트웨어 개발 방법론중 하나입니다</p>
<p>제품 개발하고 테스트하고 피드백을 받고 다시 적용하여 제품 개발을 하는 짧은 주기를 반복하는 방식으로</p>
<p><strong>환경과 상황에 맞춰 빠르고 유연하게 일하자</strong> 를 슬로건인 방법론으로 발전했습니다 </p>
<p>이 방법론을 <strong>“조금 더 현실세계에 내려오게 해서 실무에 어떤 식으로 녹여들 수 있을까?”</strong>에는 </p>
<p>정말 많은 방법들이 있습니다 </p>
<p>이 방법들의 공통적인 방식은 개발 업무를 <strong>짧은 주기</strong>로 <strong>반복</strong>한다는 것입니다 </p>
<p><del>애자일에 관련해서 서치하시다 보면 이런 그림 정말 꽤 많이 접하게 되실 겁니다</del></p>
<p><img src="https://velog.velcdn.com/images/collin-jeong/post/8d54b279-1d37-4cc3-a156-836210e185db/image.png" alt=""></p>
<ol>
<li>요구사항 정의 ( 혹은 데이터에 기반한 가설 설립 )</li>
<li>기획 및 디자인 및 개발 ( 팀원들의 열일!! )</li>
<li>테스트</li>
<li>배포</li>
<li>리뷰 ( 가설의 검증 )</li>
<li>다시 1로 돌아갓!!</li>
</ol>
<p>이와 같은 업무의 흐름을 짧은 주기로 반복하며 상품의 개발을 완성하는 소프트웨어 개발론이 <strong>애.자.일</strong> 입니다!!</p>
<hr>
<p>💡 <strong>[Q1]</strong>　　아니 <strong>“환경과 상황에 맞춰 빠르고 유연하게 일하자”</strong> 이거 좋은 것 알겠는데 
&ensp;　　　　　　그거랑 <strong>“개발 업무를 짧은 주기로 반복”</strong> 이거랑 뭔 상관인데..??
&ensp;　　　　　　(사실 제가 궁금 했었던건데…)</p>
<br/>
그렇다면 이렇게 한 번 생각을 해봅시다 

<p>여러분이 MBTI 관련 프로젝트를 새로 시작했다고 생각해봅시다</p>
<p>욕심이 엄청나게 많은 PM(여러분) 이것 저것 기능을 붙힙니다 </p>
<p>해당 프로젝트 수행하는데 6개월이 필요해서 열심히 해서 상품을 냈는데… </p>
<p>이미 그 전과 다르게 MBTI는 유행이 지났고 예전과 같지 않다 라면 프로젝트는 성공적이지 못하겠죠</p>
<p>하지만 개발 업무를 <strong>짧은 주기</strong>로 <strong>반복</strong>해서 <strong>진짜 동작하고 가치있는 최소한의 기능 (MVP)</strong>를 </p>
<p>부족한 점이 많지만 시장에 내놓고 반응을 받고 MBTI의 유행이 시들해질 쯤에는 </p>
<p>다음 개발 주기에는 내 성격에 맞는 국밥을 추천해준다던지 이런 식으로 </p>
<p><strong><em>환경과 상황에 맞춰 빠르고 유연하게</em></strong> 서비스를 제공할 수 있습니다</p>
<h2 id="agile-이-왤케-관심을-받게-됐을까">Agile 이 왤케 관심을 받게 됐을까..??</h2>
<br/>
  바로 위의 예시에서 유추할 수 있듯이 이렇게 업무를 했을 때 `고객의 만족도` 가 올라갔기 때문입니다

<p>애자일론의 첫 번째 할 일에 아래 사항이 반영되어 있기 때문입니다</p>
<blockquote>
<ol>
<li><p>고객의 요구사항 (서비스 런칭 이후 라면 앱스토어, 구글 플레이 리뷰, cs )</p>
</li>
<li><p>데이터( 가입하세요 문구를 변경했는데 10% 사용자 증가했다 )를 의존해 가설 설립</p>
</li>
</ol>
</blockquote>
<p>  혹시나 실패했을 때도 리스크가 적고 리뷰나 회고를 통해서 </p>
<p>저희의 <strong>인사이트(가설 새우는 능력) 증가하고, 업무방식은</strong> <strong>개선됩니다</strong></p>
<p>다음 개발 주기때 기능을 삭제하거나 보안해서 고객의 만족도를 높일 수도 있습니다</p>
<p>💡 아하! <strong>환경과 상황에 맞춰 빠르고 유연하게</strong> 서비스를 제공하면 <strong><code>고객의 만족도</code></strong>가 올라가구나</p>
<h2 id="애자일을-하는-팀원들과-공유해야-할-마인드">애자일을 하는 팀원들과 공유해야 할 마인드</h2>
<p><strong>애.자.일 선.언.문</strong> 에 정신이 담겨 있습니다</p>
<blockquote>
<p>우리는 소프트웨어를 개발하고, 또 다른 사람의 개발을 도와주면서 
소프트웨어 개발의 더 나은 방법들을 찾아가고 있다.
이 작업을 통해 우리는 다음을 가치 있게 여기게 되었다.</p>
</blockquote>
<blockquote>
<ol>
<li>공정과 도구보다 <strong>개인과 상호작용</strong>을</li>
<li>포괄적인 문서보다 <strong>작동하는 소프트웨어</strong>를</li>
<li>계약 협상보다 <strong>고객과의 협력</strong>을</li>
<li>계획을 따르기보다 <strong>변화에 대응하기</strong>를</li>
</ol>
</blockquote>
<blockquote>
<p>가치있게 여긴다. 이 말은, 왼쪽에 있는 것들도 가치가 있지만, 우리는 오른쪽에 있는 것들에 더 높은 가치를 둔다는 것이다.</p>
</blockquote>
<p>1, 2 번은 빠르게 대처하기 위해 중간 처리 과정을 생략한 것이고</p>
<p>3 번은 고객의 요구사항을 최대한 반영하여 만족도를 높이고</p>
<p>4번 유연하게 대처하자는 의미라고 생각합니다</p>
<p>를 쉬운 말로 풀면</p>
<blockquote>
<p>자, 우리는 팀이니까 따로 놀지말고 커뮤니케이션을 잘하고!</p>
<p>쓸데없는 문서 작성에 너무 시간 뺏기지 말고 일단 작동하는 뭔가를 만들고!</p>
<p>고객이 실제로 가치를 느낄 수 있는 프로덕트를 만들고!</p>
<p>계획은 늘상 변하는 거니까 변화에 대응할 수 있게 유연한 작업 방식으로 일하자!</p>
</blockquote>
<p>(<a href="https://evan-moon.github.io/2019/07/02/what-is-agile/">https://evan-moon.github.io/2019/07/02/what-is-agile/</a> 에서 발췌했습니다)</p>
<p>를 애자일 프로세스를 하는 팀원들과 공유한다면 훨씬 효율적으로 업무를 진행 할 수 있을 것 같습니다</p>
<h2 id="방법-그럼-어케하지-어케해야-애자일하게-일할-수-있는데">[방법] 그럼 어케하지..?? 어케해야 애자일하게 일할 수 있는데?</h2>
<p>애자일하게 일하는 엄청나게 많은 도구들이 존재합니다 </p>
<p>스크럼, 칸반, extream programming, Lean 이 중에서 제일 보편화 되어 있는 <strong>스크럼</strong>이 있습니다</p>
<p><img src="https://velog.velcdn.com/images/collin-jeong/post/27ef76fc-3d88-4666-a53a-891ce7742269/image.png" alt=""></p>
<p><a href="https://www.youtube.com/watch?v=2ukuT00ubuk&amp;t=11s">https://www.youtube.com/watch?v=2ukuT00ubuk&amp;t=11s</a> 에서 발췌했습니다</p>
<p>이와 같은 주기로 반복 될텐데 뒤에서 더 자세히 설명드리겠습니다</p>
<h2 id="팀원의-역할-개발자-디자이너-기획자-모이면-할-수-있는-건가요">[팀원의 역할] 개발자, 디자이너, 기획자 모이면 할 수 있는 건가요?</h2>
<h3 id="❌❌❌❌❌❌❌❌❌❌❌❌❌">❌❌❌❌❌❌❌❌❌❌❌❌❌</h3>
<p>사실 애자일 방식은 개발이 아닌 어떠한 <strong><em>환경과 상황이 급변하는</em></strong> 분야에 사용하셔도 무방합니다</p>
<p>그래서 꼭 개발자, 디자이너, 기획자가 아니더라도 애자일 방식으로 업무를 하는 건 상관 없는거죠 </p>
<p>대신!!! 아래의 역할을 담당하는 인원들은 <strong>꼭 있어야 합니다</strong> </p>
<p><del>하지만 저희는 IT 씬에 있기에 개발자, 디자이너, 프로젝트를 리드하는 인원이 있다고 가정하겠습니다</del></p>
<ol>
<li>PO(Project Owner) -  프로젝트를 요구사항 or 비전에 맞는 제품들을 만들도록 이끌어야합니다</li>
<li>SM(Scrum Master) - 제품들을 낭비 없이 빠르게 만들도록 노력해야합니다</li>
<li>팀원 - 제품을 옳바르게 (변화에 대응할 수 있게, 유지보수 용이하게) 만들어야 합니다</li>
</ol>
<p>쨔잔!!! 쉽죠잉? 그럼 각각 인원의 역할에 대해서 더 자세히 설명 드리겠습니다</p>
<ul>
<li>PO</li>
</ul>
<ol>
<li><p>애자일 정신의 3번에 집중</p>
</li>
<li><p>팀이 개발할 요구사항에 대한 오너쉽을 가진 사람입니다 </p>
</li>
<li><p>고객, 사용자, 팀원과 대화하여 할일의 우선순위를 선정합니다</p>
<p>⇒  물론 팀원과 이야기도 하여 할일을 정하기도 하겠지만 </p>
<p>&ensp;&ensp;사용자의 입장에서 주로 생각하는 역할이라 개발 영역에 있지 않으면서 </p>
<p>&ensp;&ensp;요구사항을 정리할 수 있는 역할이 적합합니다</p>
</li>
</ol>
<ul>
<li>SM</li>
</ul>
<ol>
<li><p>애자일 정신의 1, 2 번을 프로젝트원들이 집중하도록 도와줌</p>
</li>
<li><p>낭비 없이 빠르게 제품을 개발하는 경우는 협업에 불필요한 작업이 없고</p>
<p> 약속된 규칙들을 잘 수행했을 때 입니다</p>
<p> SM은 스크럼의 약속들을 잘 만들고 잘 지킬 수 있도록 가이드 해주는 역할</p>
</li>
<li><p>해당 팀원에게 장애가 발생했다면 같이 해결해주는 역할</p>
<p>⇒  업무문화를 개선하고자 하는 의지를 가진 사람이 적합합니다 </p>
</li>
</ol>
<ul>
<li>팀원</li>
</ul>
<ol>
<li><p>애자일 정신의 4번에 집중 </p>
</li>
<li><p>PO, SM을 제외한 모든 프로젝트원입니다 </p>
</li>
<li><p>최대한 제품을 옳바르게 (변화에 대응할 수 있게, 유지보수 용이하게) 만들어야 합니다</p>
</li>
</ol>
<br/>
EX) 개발자 - 클린 아키텍처, 클린 코드

<p>EX) 디자이너 - 컴포넌트 따고 인스턴스 생성해서 사용 추후 컴포넌트만 수정시 인스턴스 일괄 수정</p>
<p><strong>각 인원들의 추구하는 3박자를 만족시키고 서로에게 시너지를 내며 업무를 진행하는 것이 애자일하게 일하기!!!</strong></p>
<hr>
<p>PO 입장에서의 애자일 진행할 때 고려사항에 대해 너무 잘 정리한 영상을 공유드리는데 </p>
<p>영어로 되어있어서 저도 일시정지 뒤로 가기 얼마나 눌러댔는지 모르겠습니다 </p>
<p>스크럼 전체가 어떻게 흘러가는지도 너무 잘 설명되어 있는 영상이라 한 번 보시면 도움 분명히 될 것 같습니다</p>
<p><a href="https://www.youtube.com/watch?v=502ILHjX9EE">https://www.youtube.com/watch?v=502ILHjX9EE</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nodejs 이벤트루프, 논블로킹, 비동기]]></title>
            <link>https://velog.io/@collin-jeong/Nodejs-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A3%A8%ED%94%84-%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9-%EB%B9%84%EB%8F%99%EA%B8%B0</link>
            <guid>https://velog.io/@collin-jeong/Nodejs-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A3%A8%ED%94%84-%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9-%EB%B9%84%EB%8F%99%EA%B8%B0</guid>
            <pubDate>Sat, 12 Nov 2022 15:02:13 GMT</pubDate>
            <description><![CDATA[<ul>
<li>INTRO</li>
</ul>
<p>nodejs - 크롬 V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임이다.  이벤트 기반, 논블로킹 I/O모델을 사용해서 가볍고 효율적이다</p>
<p>(자바스크립트로 코딩 돌아가게 해주는 고마운 놈)  </p>
<p>코드가 실행되는 내부과정</p>
<p>큰 틀에서 우리가 흔히 아는 스택구조에 함수들이 push된다</p>
<p>하지만 중간에 만약 실행시간이 오래 걸리는 함수가 있을 경우 이후에 실행되는 함수가 딜레이되기 시작한다.</p>
<p>이 같은 경우를 방지하기 위해서 콜백이라는 개념을 사용한다. </p>
<ul>
<li>EX)</li>
</ul>
<pre><code class="language-jsx">function run(){

console.log(&#39;3초 후 실행&#39;);

}

console.log(&#39;시작&#39;);

setTimeout(run,3000);

console.log(&#39;끝&#39;);</code></pre>
<p>======콘솔======</p>
<p>시작</p>
<p>끝</p>
<p>3초 후 실행</p>
<p>setTimeout(run,3000)은 3000ms이후에 코드를 실행한다 즉, run을 콜백으로 던진다는 라인이다</p>
<p>오랜시간을 소요하는 테스크를 시뮬레이션하기 위함이다.</p>
<p>실행결과에서 알 수 있듯이 콜백으로 던진 run이 실행되기 전에 console.log(&#39;끝&#39;)이 찍힌다</p>
<p>그렇다면 setTimeout이 던진 콜백인 run이 내부적으로 호출스택에 언제 들어가는지 알아보자</p>
<p>이를 알기 위해서는 이벤트 루프, 태스크 큐, 백그라운드 3가지 개념이 필요하다</p>
<p>이벤트 루프</p>
<ul>
<li>이벤트 발생시 호출할 콜백함수들을 관리하고,  호출된 콜백 함수의 실행 순서를 결정하는 역할</li>
<li>노드가 종료 될 때 까지 이벤트 처리 위한 작업을 반복한다</li>
</ul>
<p>태스크 큐</p>
<ul>
<li>이벤트 발생 후 호출되어야 할 콜백 함수들이 기다리는 공간</li>
<li>콜백들이 이벤트 루프가 정한 순서대로 줄을 서있으므로 콜백 큐라고도 한다</li>
</ul>
<p>백그라운드</p>
<ul>
<li><p>타이머나 I/O 작업 콜백 또는 이벤트 리스너들이 대기하는 곳이다</p>
</li>
<li><p>EX2)</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/collin-jeong/post/630a9d74-0cde-4c0c-a055-a34290e7ae0e/image.png" alt=""></p>
<p>호출스택에 함수들이 push 되고 실행된다 </p>
<p>setTimeout(run,3000) 백그라운드로 이동하고 </p>
<p>3초 뒤에 백그라운드에서 run을 태스크 큐에 넘긴다 </p>
<p>호출 스택이 다 비워질 때 까지 대기한다</p>
<p>호출 스택이 다 비워지고 난 뒤에 태스크 큐에 있는 콜백들은 다시 호출 스택에 들어가게 된다</p>
<p>이와 같은 과정을 이벤트 루프라고 한다.</p>
<ul>
<li>CF)</li>
</ul>
<p>백그라운드에서 3초 후에 run을 태스크 큐에 넘겨도</p>
<p>만약 호출 스택에 함수들이 너무 많이 차 있다면 run이 바로 호출 스택에 들어가지 못한다</p>
<p>즉, setTimeout의 시간이 정확하지 않을 수도 있다.</p>
<p>부제 : 논블로킹</p>
<p>사실 이 같은 과정이 논블로킹 방식과 매우 흡사하다</p>
<p>백그라운드를 </p>
<ul>
<li>타이머나 I/O 작업 콜백 또는 이벤트 리스너들이 대기하는 곳이다</li>
</ul>
<p>이와 같이 설명하였지만,</p>
<p>오래 걸리는 함수를 백그라운드로 보내서 다음 코드 먼저 실행되고</p>
<p>오래 걸리는 함수가 다시 태스크 큐를 거쳐 호출 스택으로 올라오기 기다리는 방식이 </p>
<p>논블로킹 방식이다</p>
<ul>
<li>EX3)</li>
</ul>
<p>블로킹 방식</p>
<pre><code class="language-jsx">function longRunningTask(){

// 오래 걸리는 작업

console.log(&#39;작업 끝&#39;);

}

console.log(&#39;시작&#39;);

longRunningTask();

console.log(&#39;다음 작업&#39;);</code></pre>
<p>======콘솔=====</p>
<p>시작</p>
<p>작업 끝</p>
<p>다음 작업</p>
<p>논블로킹방식</p>
<pre><code class="language-jsx">function longRunningTask(){

// 오래 걸리는 작업

console.log(&#39;작업 끝&#39;);

}

console.log(&#39;시작&#39;);

setTimeout(longRunningTask,0);

console.log(&#39;다음 작업&#39;);</code></pre>
<p>======콘솔=====</p>
<p>시작</p>
<p>다음 작업</p>
<p>작업 끝</p>
<p>setTimeout(longRunningTask,0);은 0초후에 실행된다 </p>
<p>즉, 바로 실행된다 위 아래의 코드가 왜 다른가라고 생각할 수 도 있는데</p>
<p>앞의 이벤트 루프를 이해했다면 그렇지 않을 것이다</p>
<p>이해하지 못했다면 이벤트 루프 부분을 다시 한번 이해해보자</p>
<p>동기와 비동기</p>
<p>우리가 앞서 익혀왔던 내용들은 비동기-논블로킹에 관한 이야기이다 비교하기 위해</p>
<p>동기-블로킹이야기도 할 예정인데 혼돈되지 않길 바란다.</p>
<p>동기, 비동기, 블로킹, 논블로킹 이 네가지 용어들은 노드에서 혼용되어서 사용된다</p>
<p>하지만 용어에 차이가 있듯이 의미에 차이도 분명히 존재한다</p>
<p>굉장히 잘 정리되어 있는 글이 있어 참조한다</p>
<p><a href="https://deveric.tistory.com/99">https://deveric.tistory.com/99</a></p>
<p>비동기와 동기 블로킹 논블로킹 굉장히 잘 설명되어 있는 글이다</p>
<p>저 글을 숙지하고 다음 단계로 넘어가 볼까한다</p>
<p>다음은 비동기-논블로킹이 굉장히 효율적인 측면에서 좋아서 장점은 사용하고 싶은데</p>
<p>단점이 콜백 패턴의 처리 순서를 보장해 주지 않는다</p>
<ul>
<li><p>EX4)</p>
<pre><code> 예시로 비동기 매서드중 하나인 fs.readFile을 사용해보겠다</code></pre></li>
</ul>
<pre><code class="language-jsx">const fs = require(&#39;fs&#39;);

console.log(&#39;시작&#39;);

fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
    if (err){
        throw err;
    }
    console.log(&#39;1번&#39;, data.toString());
});

fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
    if (err){
        throw err;
    }
    console.log(&#39;2번&#39;, data.toString());
});

fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
    if (err){
        throw err;
    }
    console.log(&#39;3번&#39;, data.toString());
});

console.log(&#39;끝&#39;);</code></pre>
<p>콘솔</p>
<p>시작</p>
<p>끝</p>
<p>2번</p>
<p>3번</p>
<p>1번</p>
<p>앞서 배운 지식을 토대로 순서 결과가 시작, 끝, (1,2,3) 이정도는 예상이 가능할텐데</p>
<p>2,1,3이 지 멋대로 나오는데 정상이다 이는 반복할 때 마다 결과가 계속 바뀔 것이다</p>
<p>그래서 이 같이 콜백의 처리 순서 보장을 위해 사용하는 여러가지 방법들이 있다</p>
<p>아예 비동기적인 메서드가 아닌 동기적인 메서드를 사용하기도 하고 </p>
<p>아니면 흔히 콜백지옥이라고 부르는 아래의 방법을 하기도 한다</p>
<ul>
<li>콜백지옥</li>
</ul>
<pre><code class="language-jsx">const fs = require(&#39;fs&#39;);

console.log(&#39;시작&#39;);

fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
    if (err){
        throw err;
    }
    console.log(&#39;1번&#39;, data.toString());
    fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
        if (err){
            throw err;
        }
        console.log(&#39;2번&#39;, data.toString());
        fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
            if (err){
                throw err;
            }
            console.log(&#39;3번&#39;, data.toString());
        });
    });
});

console.log(&#39;끝&#39;);</code></pre>
<p>호출스택에 있는 readfile이 파일요청을 백그라운드에 보내고 </p>
<p>백그라운드에서 태스크 큐로 콜백이 넘어가고</p>
<p>콜백이 호출스택에 쌓이는데 호출스택에 쌓인 함수를 실행하니 </p>
<p>다시 위와 같은 과정을 반복하는 것이다 물론 콜백 순서는 보장될지 언정 가독성과 </p>
<p>코드가 너무 꼬여서 수정시 굉장히 어렵다 </p>
<p>그래서 우리는 promise를 통해서 이를 해결한다
이를 알기 위해서는 이벤트 루프, 태스크 큐, 백그라운드 3가지 개념이 필요하다</p>
<p>이벤트 루프</p>
<ul>
<li>이벤트 발생시 호출할 콜백함수들을 관리하고,  호출된 콜백 함수의 실행 순서를 결정하는 역할</li>
<li>노드가 종료 될 때 까지 이벤트 처리 위한 작업을 반복한다</li>
</ul>
<p>태스크 큐</p>
<ul>
<li>이벤트 발생 후 호출되어야 할 콜백 함수들이 기다리는 공간</li>
<li>콜백들이 이벤트 루프가 정한 순서대로 줄을 서있으므로 콜백 큐라고도 한다</li>
</ul>
<p>백그라운드</p>
<ul>
<li><p>타이머나 I/O 작업 콜백 또는 이벤트 리스너들이 대기하는 곳이다</p>
</li>
<li><p>EX2)
<img src="https://velog.velcdn.com/images/collin-jeong/post/3425fa8b-c1a3-40ba-9d84-cb8d96a452ab/image.png" alt=""></p>
</li>
</ul>
<p>호출스택에 함수들이 push 되고 실행된다 </p>
<p>setTimeout(run,3000) 백그라운드로 이동하고 </p>
<p>3초 뒤에 백그라운드에서 run을 태스크 큐에 넘긴다 </p>
<p>호출 스택이 다 비워질 때 까지 대기한다</p>
<p>호출 스택이 다 비워지고 난 뒤에 태스크 큐에 있는 콜백들은 다시 호출 스택에 들어가게 된다</p>
<p>이와 같은 과정을 이벤트 루프라고 한다.</p>
<ul>
<li>CF)</li>
</ul>
<p>백그라운드에서 3초 후에 run을 태스크 큐에 넘겨도</p>
<p>만약 호출 스택에 함수들이 너무 많이 차 있다면 run이 바로 호출 스택에 들어가지 못한다</p>
<p>즉, setTimeout의 시간이 정확하지 않을 수도 있다.</p>
<p>부제 : 논블로킹</p>
<p>사실 이 같은 과정이 논블로킹 방식과 매우 흡사하다</p>
<p>백그라운드를 </p>
<ul>
<li>타이머나 I/O 작업 콜백 또는 이벤트 리스너들이 대기하는 곳이다</li>
</ul>
<p>이와 같이 설명하였지만,</p>
<p>오래 걸리는 함수를 백그라운드로 보내서 다음 코드 먼저 실행되고</p>
<p>오래 걸리는 함수가 다시 태스크 큐를 거쳐 호출 스택으로 올라오기 기다리는 방식이 </p>
<p>논블로킹 방식이다</p>
<ul>
<li>EX3)</li>
</ul>
<p>블로킹 방식</p>
<pre><code class="language-jsx">function longRunningTask(){

// 오래 걸리는 작업

console.log(&#39;작업 끝&#39;);

}

console.log(&#39;시작&#39;);

longRunningTask();

console.log(&#39;다음 작업&#39;);</code></pre>
<p>======콘솔=====</p>
<p>시작</p>
<p>작업 끝</p>
<p>다음 작업</p>
<p>논블로킹방식</p>
<pre><code class="language-jsx">function longRunningTask(){

// 오래 걸리는 작업

console.log(&#39;작업 끝&#39;);

}

console.log(&#39;시작&#39;);

setTimeout(longRunningTask,0);

console.log(&#39;다음 작업&#39;);</code></pre>
<p>======콘솔=====</p>
<p>시작</p>
<p>다음 작업</p>
<p>작업 끝</p>
<p>setTimeout(longRunningTask,0);은 0초후에 실행된다 </p>
<p>즉, 바로 실행된다 위 아래의 코드가 왜 다른가라고 생각할 수 도 있는데</p>
<p>앞의 이벤트 루프를 이해했다면 그렇지 않을 것이다</p>
<p>이해하지 못했다면 이벤트 루프 부분을 다시 한번 이해해보자</p>
<p>동기와 비동기</p>
<p>우리가 앞서 익혀왔던 내용들은 비동기-논블로킹에 관한 이야기이다 비교하기 위해</p>
<p>동기-블로킹이야기도 할 예정인데 혼돈되지 않길 바란다.</p>
<p>동기, 비동기, 블로킹, 논블로킹 이 네가지 용어들은 노드에서 혼용되어서 사용된다</p>
<p>하지만 용어에 차이가 있듯이 의미에 차이도 분명히 존재한다</p>
<p>굉장히 잘 정리되어 있는 글이 있어 참조한다</p>
<p><a href="https://deveric.tistory.com/99">https://deveric.tistory.com/99</a></p>
<p>비동기와 동기 블로킹 논블로킹 굉장히 잘 설명되어 있는 글이다</p>
<p>저 글을 숙지하고 다음 단계로 넘어가 볼까한다</p>
<p>다음은 비동기-논블로킹이 굉장히 효율적인 측면에서 좋아서 장점은 사용하고 싶은데</p>
<p>단점이 콜백 패턴의 처리 순서를 보장해 주지 않는다</p>
<ul>
<li><p>EX4)</p>
<pre><code> 예시로 비동기 매서드중 하나인 fs.readFile을 사용해보겠다</code></pre></li>
</ul>
<pre><code class="language-jsx">const fs = require(&#39;fs&#39;);

console.log(&#39;시작&#39;);

fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
    if (err){
        throw err;
    }
    console.log(&#39;1번&#39;, data.toString());
});

fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
    if (err){
        throw err;
    }
    console.log(&#39;2번&#39;, data.toString());
});

fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
    if (err){
        throw err;
    }
    console.log(&#39;3번&#39;, data.toString());
});

console.log(&#39;끝&#39;);</code></pre>
<p>콘솔</p>
<p>시작</p>
<p>끝</p>
<p>2번</p>
<p>3번</p>
<p>1번</p>
<p>앞서 배운 지식을 토대로 순서 결과가 시작, 끝, (1,2,3) 이정도는 예상이 가능할텐데</p>
<p>2,1,3이 지 멋대로 나오는데 정상이다 이는 반복할 때 마다 결과가 계속 바뀔 것이다</p>
<p>그래서 이 같이 콜백의 처리 순서 보장을 위해 사용하는 여러가지 방법들이 있다</p>
<p>아예 비동기적인 메서드가 아닌 동기적인 메서드를 사용하기도 하고 </p>
<p>아니면 흔히 콜백지옥이라고 부르는 아래의 방법을 하기도 한다</p>
<ul>
<li>콜백지옥</li>
</ul>
<pre><code class="language-jsx">const fs = require(&#39;fs&#39;);

console.log(&#39;시작&#39;);

fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
    if (err){
        throw err;
    }
    console.log(&#39;1번&#39;, data.toString());
    fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
        if (err){
            throw err;
        }
        console.log(&#39;2번&#39;, data.toString());
        fs.readFile(&#39;./readme2.txt&#39;,(err,data) =&gt; {
            if (err){
                throw err;
            }
            console.log(&#39;3번&#39;, data.toString());
        });
    });
});

console.log(&#39;끝&#39;);</code></pre>
<p>호출스택에 있는 readfile이 파일요청을 백그라운드에 보내고 </p>
<p>백그라운드에서 태스크 큐로 콜백이 넘어가고</p>
<p>콜백이 호출스택에 쌓이는데 호출스택에 쌓인 함수를 실행하니 </p>
<p>다시 위와 같은 과정을 반복하는 것이다 물론 콜백 순서는 보장될지 언정 가독성과 </p>
<p>코드가 너무 꼬여서 수정시 굉장히 어렵다 </p>
<p>그래서 우리는 promise를 통해서 이를 해결한다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nestjs & AWS EB(linux2) & github action] yarn berry zero install 도전기!! ]]></title>
            <link>https://velog.io/@collin-jeong/Nestjs-AWS-EBlinux2-github-action-yarn-berry-zero-install-%EB%8F%84%EC%A0%84%EA%B8%B0</link>
            <guid>https://velog.io/@collin-jeong/Nestjs-AWS-EBlinux2-github-action-yarn-berry-zero-install-%EB%8F%84%EC%A0%84%EA%B8%B0</guid>
            <pubDate>Sun, 30 Oct 2022 08:24:48 GMT</pubDate>
            <description><![CDATA[<h2 id="👋-해볼것">👋 해볼것!!!</h2>
<blockquote>
<ol>
<li>Nestjs 프로젝트 npm -&gt; yarn berry</li>
<li>aws EB 에서 npm -&gt; yarn berry (package manager 변경)</li>
<li>github action 에 ci/cd 파이프라인 변경</li>
</ol>
</blockquote>
<br/>

<h2 id="🧐-왜-yarn-berry-zero-install-에-도전하나요">🧐 왜 yarn berry zero install 에 도전하나요??</h2>
<p>(대표님께 동의를 구하고 글을 작성합니다 대표님 감사해요!!!)
현재 사내 프로젝트는 aws elastic beanstalk 에 vpc 하나에 
[private subnet] 환경 : backend,db 
[public subnet] 환경 : frontend
이렇게 총 2개의 환경으로 배포를 전개하고 있습니다
(관련한 글도 얼렁 작성해야 하는데 ㅠㅠㅠ 쓰다 말았는데 조만간 작성 하겠습니다) </p>
<p>그런데 백앤드를 배포하다가... 아래와 같은 에러를 만났습니다 </p>
<pre><code>npm ERR! code ENOMEM
npm ERR! syscall spawn
npm ERR! errno -12
npm ERR! spawn ENOMEM</code></pre><p>먼저 해야할 것!!! 역시나 구글링!!!</p>
<p><strong>원인은 메모리부족!!!!</strong></p>
<p>**[현재 상황] **
사실... 정석이라면 빌드 후의 결과만 올려서 쨔잔하고 배포하는 것이 맞는데 
계속 에러가 나는 겁니다 (해당 과정은 다른 글에 쓰도록 할게요)
그러다 본 몇 개의 글에서 코드를 eb에 올려서 npm install, npm run build 시키는 글을 보고는 
눈 찔끔 감고 했지만 마음에는 큰 죄책감이 있었어요   </p>
<p><strong>[해결을 위한 시도들]</strong></p>
<ol>
<li>우선 메모리 부족을 해결 위해서 max-old-space-size 옵션을 줘봤지만 ❌</li>
<li>역시 build를 올려서 하는게 메모리를 너무 많이 잡아 먹는다 
build한 결과물만 올리자 우여곡절 끝에 되긴 했는데 프로젝트 더 개발하니 
그 뒤에도 똑같이 에러가 나더라고요 
npm install할 때 에러가 나는 것 같았습니다 (eb-engine.log)
(혹시 선배님들 제가 잘못 알고 있는 것이 있다면 따끔하게 말씀해주시면 감사하겠습니다)</li>
</ol>
<p>결국 npm install 이 문제라고 판단했고 
얼마전 저희 회사 CTO분이 npm, yarn, yarn berry 에 대해서 이야기 해주셔서 
yarn berry zero install에 도전하게 되었습니다</p>
<p><del>(저는 yarn을 dependency에 대한 안전성이 더 높다고 판단해서 선호하는 편인데 
aws eb 에서 yarn을 사용하기 위해선 추가적으로 진행해야하는 작업들이 
프로젝트 처음 ci/cd 파이프라인을 설계할 때는 너무 어렵게 느껴졌습니다 
하지만 지금은 충분히 맞을 준비가 되어 있어서 도전!! 해보기로 했습니다)</del></p>
<blockquote>
<p>** yarn berry zero install에 도전**</p>
</blockquote>
<br/>

<hr>
<br/>

<h2 id="👆-nestjs-프로젝트-npm---yarn-berry">👆 Nestjs 프로젝트 npm -&gt; yarn berry</h2>
<p>생각보다 쉬웠습니다 </p>
<pre><code>1. node_modules 삭제
2. package-lock.json 삭제
3. npm install -g yarn
4. yarn set version berry
5. yarn
6. yarn add -D typescript
7. yarn dlx @yarnpkg/sdks vscode</code></pre><br/>

<h2 id="✌️-aws-eb-에서-npm---yarn-berry-package-manager-변경">✌️ aws EB 에서 npm -&gt; yarn berry (package manager 변경)</h2>
<p>사실 여기가 저 같은 백앤드 주니어한테는 너무 어려웠습니다 ㅠㅠㅠㅠ
저는 아래와 같은 순서로 npm -&gt; yarn berry로 package manager를 변경하려합니다</p>
<blockquote>
<ol>
<li>eb에서 npm 을 사용하지 않도록 합니다</li>
<li>Nest 프로젝트 빌드 결과물을 배포전에 eb에 yarn berry 설정을 진행합니다 </li>
<li>배포 결과물을 실행합니다  </li>
</ol>
</blockquote>
<p>아래와 같이 디렉토리 구조를 가져갔습니다 postdeploy, prebuild, predeploy와 같은 용어들은
eb lifecycle에 관련된 내용입니다</p>
<pre><code>.platform
└── hooks
    ├── postdeploy
    │   └── del-nodemodules.sh
    ├── prebuild
    │   └── prevent-npm.sh
    └── predeploy
        └── yarn.sh</code></pre><p><img src="https://velog.velcdn.com/images/collin-jeong/post/be165f4d-1e34-4aa3-b61d-746f24c601d4/image.png" alt="">
<a href="https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/platforms-linux-extend.html">https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/platforms-linux-extend.html</a>
aws eb docs도 같이 공유합니다
<br/></p>
<h3 id="1-eb에서-npm-을-사용하지-않도록-합니다">1. [eb에서 npm 을 사용하지 않도록 합니다]</h3>
<p>사실 전 2번만 잘 실행하면 알아서 1번이 되는 줄 알았는데 amazon linux2 에서 
npm 을 실행시키지 않는 방법에 대한 한 줄기의 빛이 되어 주셨던 글 먼저 공유하겠습니다
<a href="https://stackoverflow.com/questions/41657226/customize-aws-elasticbeanstalk-nodejs-install-use-yarn">https://stackoverflow.com/questions/41657226/customize-aws-elasticbeanstalk-nodejs-install-use-yarn</a>
이 글의 제일 마지막 댓글을 보시게 되면 </p>
<blockquote>
<p><strong>node_modules 폴더가 존재하면 npm install 을 실행하지 않습니다!!!!</strong></p>
</blockquote>
<p>실제로도 저 방법 이외는 eb 는 무적권 npm install --production을 실행시키더라고요 ㅠㅠㅠ</p>
<p>eb에서 알아서 npm install --production을 실행시키기 전에 node_modules 라는 이름의 빈 폴더를 
만드는 방식으로 eb에서 npm install --production을 실행시키지 않도록 했습니다 </p>
<p><code>prevent-npm.sh</code></p>
<pre><code class="language-bash">#!/bin/bash
# 빈 node_modules 생성시 npm install --production 실행X
app=&quot;/var/app/staging/&quot;
cd &quot;${app}&quot;
mkdir node_modules</code></pre>
<br/>


<h3 id="2-nest-프로젝트-빌드-결과물을-배포전에-eb에-yarn-berry-설정을-진행합니다">2. [Nest 프로젝트 빌드 결과물을 배포전에 eb에 yarn berry 설정을 진행합니다]</h3>
<p><code>yarn.sh</code> </p>
<pre><code class="language-bash">#!/bin/bash

# install node
curl --silent --location https://rpm.nodesource.com/setup_16.x | sudo bash -;

# install yarn
curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo;
yum -y install yarn;
yarn set version berry

# 환경변수를 변경했을때 아래의 설정을 하지 않으면 잘 동작하지 않을 수 있다고 합니다
mkdir -p .platform/confighooks/{prebuild,predeploy}
ln -s ../../hooks/predeploy/yarn.sh .platform/confighooks/predeploy/yarn.sh
ln -s ../../hooks/prebuild/prevent-npm.sh .platform/confighooks/prebuild/prevent-npm.sh
</code></pre>
<p><strong>💦 [마주쳤던 어리둥절 1]</strong></p>
<p>yarn berry zero install을 목표로 달려가고 있는데 node_modules가 존재하면 문제가 된다고 생각해서
빈 폴더인 node_modules를 삭제하는 쉘 스크립트(디버깅 포함)를 작성했는데... </p>
<p><code>del-nodemodules.sh</code> </p>
<pre><code class="language-bash">#!/bin/bash
ls -l
rm -rf node_modules
ls -l</code></pre>
<p>이미 첫번째 ls -l 명령어를 실행했을때 이미 node_modules는 존재하지 않았습니다 
<del>혹시 선배님들 중에서 아시는 분이 있으시다면 알려주시면 감사하겠습니다</del></p>
<h3 id="3-배포-결과물을-실행합니다">3. 배포 결과물을 실행합니다</h3>
<p>AWS EB는 프로젝트 최상단 경로에서 app.js, server.js, Procfile을 찾고 실행시킵니다</p>
<p><code>Procfile</code></p>
<pre><code class="language-bash">web: yarn run start:prod</code></pre>
<p><code>package.json</code></p>
<pre><code class="language-json">{
  ...
  &quot;scripts&quot;:{
    ...
    &quot;start:prod&quot;: &quot;cross-env NODE_ENV=production node dist/main&quot;,
    ...
  }
  ...
}</code></pre>
<hr>
<br/>

<h2 id="🤟-github-action-에-cicd-파이프라인-변경">🤟 github action 에 ci/cd 파이프라인 변경</h2>
<pre><code>.github
└── workflows
    └── cicd.yml</code></pre><p><code>cicd.yml</code></p>
<pre><code class="language-bash">name: cicd

on:
  push:
    branches: [ &quot;main&quot;, &quot;develop&quot; ]

jobs:
  deploy:
    name: CD Pipeline
    runs-on: ubuntu-18.04

    strategy:
      matrix:
        node-version: [&#39;16.x&#39;]
    steps:
      - uses: actions/checkout@v2

      - name: Run build
        run: |
          corepack yarn
          yarn set version berry
          yarn run build

      # Install AWS CLI 2
      - name: Install AWS CLI 2
        run: |
          curl &quot;https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip&quot; -o &quot;awscliv2.zip&quot;
          unzip awscliv2.zip
          which aws
          sudo ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli --update

      # Configure AWS credentials
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      # Make ZIP file with source code
      # -x는 zip파일 생성 시에 해당 부분들을 제외한다.
      - name: Generate deployment package
        run: zip -r deploy.zip . -x &#39;*.git*&#39; &#39;./aws/*&#39; &#39;./src/*&#39; &#39;./node_modules/*&#39; &#39;./test/*&#39; awscliv2.zip

      # Deploy to Elastic Beanstalk
      # application_name과 environment_name을 꼭 확인하자!
      # 해당 부분은 꼭 같아야 한다!!
      - name: Deploy to EB Prod
        if: ${{ github.ref == &#39;refs/heads/main&#39; }}
        uses: einaregilsson/beanstalk-deploy@v14
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: [운영시 어플리케이션 이름]
          environment_name: [운영시 환경 이름]
          region: ${{ secrets.AWS_REGION }}
          version_label: ${{github.SHA}}
          deployment_package: deploy.zip

      - name: Deploy to EB QA
        if: ${{ github.ref == &#39;refs/heads/develop&#39; }}
        uses: einaregilsson/beanstalk-deploy@v14
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: [qa시 어플리케이션 이름]
          environment_name: [qa시 환경 이름]
          region: ${{ secrets.AWS_REGION }}
          version_label: ${{github.SHA}}
          deployment_package: deploy.zip</code></pre>
<p>저는 QA와 운영 환경을 각각 사용하고 있는데 git branch 에 따라서 다른 환경에 배포하고 있습니다</p>
<p><del>Nestjs 를 사용하면서 부끄럽지만 테스트 코드를 작성하지 않고 있습니다
혼자 백앤드를 맡아서 사내 프로젝트의 mvp를 만들고 있고 
nestjs &amp; typeorm을 처음으로 실무에서 사용하고 있어서 조금 익숙해지고 
mvp를 운영 배포 이후에 바로 테스트 코드 <strong>꼭!!!</strong> <strong>꼭!!!</strong> 작성할 것입니다 
(github action yml 파일에 ㅠㅠㅠ 테스트 부분이 없어도 양해부탁드립니다)</del>
<br/></p>
<hr>
<h2 id="❗️느낀점">❗️느낀점</h2>
<p>사실 처음 eb를 접하게 된 이유가 혼자 운영, 개발 등을 맡아서 프로젝트 하나를 완성해야 하는데...
러닝커브를 고려한 운영쪽의 공수를 최소한으로 하고 싶었고 aws의 멋진 엔지니어분들과 회의 끝에
<code>elastic beanstalk 한 번 해보자!!</code> 가 됐습니다</p>
<p>하지만 좀 더 세부적인 내용을 적용시키려하니 제가 아직 많이 모자라서 쉽지 않았습니다
특히 파이프라인을 설계하고 진행하는 내용은 아직 시작하기 전부터  부담감이 많이 느껴지는 것 같지만
해냈을 때의 이 성취감 때문에 절대 끊을 수 없죠 ㅋㅋㅋㅋ </p>
<p>앞으로도 늘 생각하고 고민하고 성장하는 프로그래머가 되기 위해서 노력 해야겠습니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS Elastic Beanstalk] Github Action으로 nestjs & react 웹 서비스 배포하기 - 0]]></title>
            <link>https://velog.io/@collin-jeong/AWS-Elastic-Beanstalk-Github-Action%EC%9C%BC%EB%A1%9C-nestjs-react-%EC%9B%B9-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@collin-jeong/AWS-Elastic-Beanstalk-Github-Action%EC%9C%BC%EB%A1%9C-nestjs-react-%EC%9B%B9-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Sat, 13 Aug 2022 14:12:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h2 id="시작하기전-">시작하기전 !!</h2>
</blockquote>
<br/>
첫 글이라 다른 분들의 글에 비해 가독성있게 글을 못쓰더라도 양해부탁드립니다

<p><strong>Elastic Beanstalk</strong>을 서비스 운영을 위해서 사용하게 되면서 부딪혔던 문제들을 정리하기 위해서
제가 도움받았던 글들 내용들을 기억 못하기 전에 쭈우욱 기록해볼까 합니다</p>
<p>데이드림랩이라는 회사에서 신나게 코딩하고 있는 쥬니어 of 쥬니어 백앤드 엔지니어입니다 
<del>이름은 부끄러워서... ㅠㅠㅠ</del>
이번에 새로운 서비스 MVP를 뽑는데
어떤 환경에서 서비스를 배포할지 너무 고민이 많았습니다</p>
<p>해당 프로젝트의 총 인원은 3명 (대표님, 백앤드 1명, 프론트 1명)
프론트는 <strong>React</strong>, 백앤드는 <strong>Nestjs</strong>를 선택하였고
운영에 최대한 부담을 줄고 싶어서 여러가지 서비스를 알아보다가 
<strong>AWS Elastic Beanstalk(EB)</strong>을 알게 되었는데 홀딱 반해서 인프라 환경으로 채택하였습니다
(사실 여러 장점들은 더 찾아보시면 알 수 있으실 거에요)</p>
<p>CI/CD는 <strong>Github Action</strong>을 사용해보고 싶었습니다 
여러가지 툴이 있었지만 운영에 필요한 리소스를 최소화 하고 싶었기 때문에 
<strong>젠킨스</strong>를 사용하기 위해 빌드 서버를 따로 구축하는 것 보다
<strong>Github Action</strong>을 사용하면 좀 더 수월하지 않을까 라고 생각했습니다
무엇보다 반한 점은 <strong>Github PR</strong> 에서 걸려서 아예 <strong>merge</strong>가 안되게 하는게 너무 좋다고 생각했습니다 
그리고 많은 예시들이 있어서 채택하게 되었습니다</p>
<p><del>(물론 조사하면서 <strong>github action</strong>의 과금 정책도 고려 대상이었습니다
근데 개발자 2명이 <code>main</code>, <code>develop</code> 브랜치에 <code>commit</code>, <code>pr</code>을 <strong>github action</strong>을 걸어 놓으면</del> 
<del>빌드시 필요한 시간 계산을 해보았는데 과금 정책 안이라고 예상했습니다)</del></p>
<p>제가 공부하면서 알게된 내용이라 잘못된 지식이 포함되어 있으면 알려주시면 감사하겠습니다</p>
<br/>

<blockquote>
<h2 id="아키텍처-스케치">아키텍처 스케치</h2>
</blockquote>
<br/>

<p><a href="https://catalog.us-east-1.prod.workshops.aws/workshops/3fd6c80b-39f2-4534-b69c-c400aed50c67/ko-KR/beanstalk/deploy-new-app">AWS Elastic Beanstalk workshop</a>의 목표 아키텍처와 같은 아키텍처를 목표로 했습니다</p>
<p align="center">
  <img src="https://velog.velcdn.com/images/collin-jeong/post/fb584cb2-029c-4393-bb2a-4192b6cde706/image.png" alt="factorio thumbnail"/>
  <출처: AWS Elastic Beanstalk WorkShop>
</p> 


<br/>  


<blockquote>
<h2 id="🛴-과정">🛴 &nbsp;과정</h2>
</blockquote>
<ol>
<li><p><strong>AWS VPC 설정</strong>  </p>
</li>
<li><p><strong>AWS IAM 설정</strong>  </p>
</li>
<li><p><strong>AWS Elastic Beanstalk 설정</strong> </p>
</li>
<li><p><strong>코드를 Elastic Beanstalk 에 업로드</strong></p>
</li>
<li><p><strong>레포에 Github Action 걸어놓기</strong></p>
</li>
</ol>
<br/>  

<blockquote>
<h3 id="1-aws-vpc">1. AWS VPC</h3>
</blockquote>
<p>VPC를 운영 환경과 테스트 환경 이렇게 총 2개를 구축하고 싶은 경우 VPC를 2개 생성해주시면 되겠습니다<br>운영 환경과 테스트 환경을 똑같이 맞춰주면 훨씬 안정성이 올라가겠죠? </p>
<p>우선 1개만 생성해보는 걸로 해보겠슴돠
AWS VPC 생성을 위해 [VPC] - [VPC 생성] 에 들어갑니당
 <img src="https://velog.velcdn.com/images/collin-jeong/post/23363487-dadb-453c-a896-8094d01c6915/image.png" alt=""></p>
<p>NAT 게이트웨이 1개의 AZ에서 진행합니다 (private subnet을 위함입니당)
<img src="https://velog.velcdn.com/images/collin-jeong/post/600693f3-94cd-476b-a8e7-2a967aaeab71/image.png" alt=""></p>
<p>그럼 아래와 같이 VPC가 구성됩니다 
<img src="https://velog.velcdn.com/images/collin-jeong/post/5ebc964d-1bd0-43b0-9757-6e593fb5f2cb/image.png" alt=""></p>
<p><del>(이렇게 간단하게 생성하는게 있는데... 처음 AWS에서 네트워크 구조를 구성하다보니 몰라서 일일히 생성도 해보고... ㅠㅠㅠ 많은 삽질이 있었습니다 여러분들은 저처럼 하지 마세요 ㅠㅠㅠㅠ)</del> </p>
<br/>

<blockquote>
<h3 id="2-aws-iam">2. AWS IAM</h3>
</blockquote>
<p>우선 Github Action으로 CI/CD 자동화를 위해서 다음과 같이 권한을 준 사용자를 생성
<img src="https://velog.velcdn.com/images/collin-jeong/post/18f4e35b-b764-47b8-a37c-358dc1f43bc7/image.png" alt=""></p>
<p>(실제 프로젝트를 진행하실때는 훨씬 세세하게 제한된 IAM을 권장드립니다)
<br/>  </p>
<blockquote>
<h3 id="3-elastic-beanstalk-설정">3. Elastic Beanstalk 설정</h3>
</blockquote>
<p>이제 Elastic Beanstalk에서 배포 해보도록 해보겠습니다
  <img src="https://velog.velcdn.com/images/collin-jeong/post/8f85520a-e96f-4084-877a-0aa4f18ebee5/image.png" alt=""></p>
<p>사실 Elastic에 어플리케이션과 환경이라는 용어가 저에게는 좀 햇갈렸습니다
환경을 생성하면 EC2 하나가 생성된다 이렇게 생각하시면 편할 것 같습니다 
저는 아까 우리가 위에서 만든 VPC의 서브넷에 우리 프로그램이 올라갈 인스턴스 하나를 넣는다 고 이해했습니다</p>
<p>(혹시 잘못 이해한 점이 있다면 댓글로 피드백 부탁드리겠습니다 ㅠㅠ)</p>
<p>  <img src="https://velog.velcdn.com/images/collin-jeong/post/7eabca0a-4954-4f85-8c62-3e6100cb5d6f/image.png" alt=""></p>
<p>저는 백앤드를 Nestjs를 사용할 것 이기 때문에 
<code>어플리케이션 이름</code>은 <code>backend</code> 
<code>플랫폼</code>은 <code>Node.js 16</code> 
어플리케이션 코드는 일단 샘플 어플리케이션으로 진행해보겠습니다 </p>
<h3 id="절대-그냥-넘어가지-마시구-꼭꼭-추가-옵션-구성으로">절대 그냥 넘어가지 마시구 꼭꼭 추가 옵션 구성으로!!!</h3>
<p>만들어놓은 VPC를 설정해줘야 합니다 </p>
]]></description>
        </item>
    </channel>
</rss>