<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev_taylor.log</title>
        <link>https://velog.io/</link>
        <description>망원동 개발자</description>
        <lastBuildDate>Thu, 11 May 2023 12:49:25 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev_taylor.log</title>
            <url>https://velog.velcdn.com/images/dev_taylor/profile/93b1f56d-e392-45fc-ad30-91661f04b627/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev_taylor.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_taylor" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Template Method Pattern]]></title>
            <link>https://velog.io/@dev_taylor/Template-Method-Pattern</link>
            <guid>https://velog.io/@dev_taylor/Template-Method-Pattern</guid>
            <pubDate>Thu, 11 May 2023 12:49:25 GMT</pubDate>
            <description><![CDATA[<p>최근 네이버 카페 검색 결과 크롤링 프로그램을 개발했다. 나름 머리를 굴려가며 범용성과 확장성을 생각하며 개발을 했지만, 아무래도 구력이 부족하다보니 부족한부분이 많았고 이 부분을 회사 동료가 rebase를 해주었다. Template Method Pattern에 대해 알아보자.</p>
<h3 id="템플릿-메서드-패턴이란">템플릿 메서드 패턴이란</h3>
<p>객체지향 디자인 패턴 중 하나, 기능의 뼈대(템플릿)과 실제 구현을 분리하는 패턴이다. 스프링으로 개발할때 몇번 공부했던 기본적인 패턴 중 하나로, 스프링에서는 보통 공통되는 기능에 대해 interface를 만든 뒤, 이를 구현하는 구현 클래스들을 여럿 생성한다. 실제 객체를 생성하는 부분에서는 코드 상 호출은 인터페이스를 호출하지만 실제 생성되는 객체는 여러 구현 클래스 중 필요한 클래스를 기반으로 객체를 생성한다.</p>
<p>상위 클래스와 하위 클래스로 나뉘는데, 이때 하위 클래스는 상위 클래스가 정의한 메서드만을 구현하게된다. 즉 어떤것을 구현하는지는 상위클래스에서 지정된다. </p>
<p>실제 내가 개발했던 코드를 보자.</p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/6a7c6e6d-1966-468d-a3a7-82dcd6284c39/image.png" alt=""></p>
<p>이런 식으로 구현이 돼있다. 기존의 다른 코드의 모양새를 참고한 부분이고, 아직 네이버 외에는 개발이 안되어 있어서 if 문으로 naver의 경우에만 처리를 하고 다른 포탈의 경우에는 pass 하도록 되어있다.</p>
<p>생각해보면, 다른 포탈이 개발되어 else에 해당 포탈을 추가하게 되면 위의 코드를 똑같이 붙여넣기 해야하고 따라서 코드 중복의 문제가 생긴다. 이 때문에, 아직 개발이 되었든 되지 않았든 동일한 코드만 사용하도록 변경이 필요하다. </p>
<p>다음은 동료가 수정해준 코드이다.
<img src="https://velog.velcdn.com/images/dev_taylor/post/69fe3c80-95c2-4b3a-b945-6f75484d931e/image.png" alt=""></p>
<p>MAPPER를 통해 어떤 포탈인지 가져오도록 하였다. 다른 포탈이 추가가 되면 이 mapper만 수정해주면 되며, 어떤 포탈이든 동일한 코드를 타게 됨으로써 코드의 중복이 확연하게 줄었다.</p>
<p>다음을 보자</p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/e560bae5-9f9d-4172-ab9f-b49dc170362f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/0fa32a75-800c-445f-822b-afec2ca7e29b/image.png" alt=""></p>
<p>NaverSearchingGateway는 KeywordSearchingBaseGateway를 상속한다. 상속을 통해 NaverSearchingGateway 객체가 생성된 곳에서 필요한 경우 KeywordSearchingBaseGateway의 메서드를 갖다 쓰는 방식으로 구현을 했는데.. 잘 보면 NaverSearchingGateway와 KeywordSearchingBaseGateway에 동일한 메서드가 전무하다. 즉 <strong><em>오버라이딩</em></strong>이 일절 되어있지 않다는것. </p>
<p>위에서 말했다시피 템플릿 메서드 패턴은 하위클래스는 상위클래스가 정의한 메서드를 쓰게 함으로써 상위클래스를 상속받는 다른 하위 클래스가 있을 경우 그 &quot;템플릿&quot;을 동일하게 가져가는것을 말하는데, 이것이 전혀 지켜지지 않은 구조라는 것.</p>
<p>수정된 코드를 보자</p>
<p>KeywordSearchingBaseGateway
<img src="https://velog.velcdn.com/images/dev_taylor/post/44790b75-a017-418a-8ae8-5fa1157a0b96/image.png" alt=""></p>
<p>NaverSearchingGateway
<img src="https://velog.velcdn.com/images/dev_taylor/post/10249a00-0f6b-418e-ab91-807b2b4d6503/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/1deb5d01-ba37-492f-a05b-926633b46133/image.png" alt=""></p>
<p>상위 클래스인 KeywordSearchingBaseGateway에서 선언만 되어있는 <strong>fetch_keyword_results,_get_header, _get_params</strong> 메서드를, 하위 클래스인 NaverSearchingGateway에서 구현을 해주었다. </p>
<p>이를 통해 하위클래스는 상위 클래스가 정의한 메서드를 쓰도록 구현을 해주었으며, 이후 이 상위클래스를 상속받는 하위클래스에서도 반드시 이 메서드들을 구현하도록 강제할 수 있다. </p>
<p>개인적으로 하나를 가지고 이리저리 짜맞추는걸 좋아해서 객체지향을 좋아하는게 있었는데...아직 한참 멀었구나 하는 생각이 들었다. 이게 다 피가되고 살이 되는거지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[pep8 - 모듈 import]]></title>
            <link>https://velog.io/@dev_taylor/pep8-%EB%AA%A8%EB%93%88-import</link>
            <guid>https://velog.io/@dev_taylor/pep8-%EB%AA%A8%EB%93%88-import</guid>
            <pubDate>Thu, 11 May 2023 12:34:43 GMT</pubDate>
            <description><![CDATA[<p>파이썬으로 개발을 한지 얼마 되지 않아 파이썬 개발시 기본이라는 pep8 양식을 지키지 않은 경우가 왕왕 발생한다. 이번에 개발한 프로그램을 코드를 보며 pep8 상 어긋나는 부분 특히 import 부분을 비교해보려한다</p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/a2bf3163-733c-4795-82c4-0c0894261f2f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/543e7142-e81c-4b0b-ab56-b09f9b6235b3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/d0bc50ea-3254-4d51-9499-1993eb029ffd/image.png" alt=""></p>
<h3 id="1-한-라인에-하나의-모듈-import">1. 한 라인에 하나의 모듈 import</h3>
<ul>
<li>import는 항상 상단에 위치</li>
<li>import는 여러 모듈을 콤마로 연결하지 않고 한 라인에 하나의 모듈을 import 한다</li>
</ul>
<h3 id="2-모듈-import-순서">2. 모듈 import 순서</h3>
<p>모듈 import 순서는 다음과 같다</p>
<ol>
<li>표준 라이브러리 모듈</li>
<li>서드파티 모듈</li>
<li>직접 만든 모듈</li>
</ol>
<p>직접 만든 모듈은 제외하고, 표준 라이브러리 모듈과 서드 파티 모듈은 어떻게 구분할까. 표준 라이브러리 모듈은 python이나 django에서 지원하는 모듈일테고 서드파티 모듈은 팀에서 공통적으로 사용하도록 개발된 모듈일것이다.</p>
<p>위의 코드상으로 보면 표준 라이브러리 모듈 다음에 서드파티 모듈이 들어가도록 구현되어있다. 다만, naver_gateway.py를 보면 직접 만든 모듈이 서드파티 모듈 상단에 위치해있다.</p>
<p>이 부분을 이렇게 바꾸자.</p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/bf3e1962-01cc-4f8c-99e8-1c403be80ca5/image.png" alt=""></p>
<h3 id="3-절대-경로-사용">3. 절대 경로 사용</h3>
<p>pep8에서는 모듈 import시 절대 경로 사용을 권장한다. 사용할 모듈이 현재 모듈과 같은 패키지 상에 있더라도 절대경로를 사용하도록 하고 있다. 유지보수시 이슈가 되는 모듈을 빠르게 찾을 수 있기 위함인듯하다.</p>
<p>위 코드를 보면 상대경로를 매우 많이 쓰고 있다...기존 레거시 코드가 상대경로를 주로 사용하고 있었고 이에 익숙해진 탓이다. 이를 고쳐보자</p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/4363e05d-4795-435e-ae4c-c3f995779934/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/d5b460a7-1d4f-4eee-9284-2dc05d2006b9/image.png" alt=""></p>
<p>훨씬 가독성이 나아졌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[celery 핵심과 customization]]></title>
            <link>https://velog.io/@dev_taylor/celery-%ED%95%B5%EC%8B%AC%EA%B3%BC-customization</link>
            <guid>https://velog.io/@dev_taylor/celery-%ED%95%B5%EC%8B%AC%EA%B3%BC-customization</guid>
            <pubDate>Fri, 17 Feb 2023 06:35:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Pycon2019의 <a href="https://www.youtube.com/watch?v=vGPyjJ1jWUs">셀러리 핵심과 커스터마이제이션</a>을 정리한 글입니다.</p>
</blockquote>
<h2 id="1-celery">1. Celery</h2>
<h3 id="celery">celery?</h3>
<p>메세지 전달을 기반으로 한 비동기 task 큐</p>
<ul>
<li>작업 : message로 표현됨</li>
<li>Client : 작업을 요청하는 주체</li>
<li>Worker : 작업을 수행하는 주체</li>
<li>Broker : 클라이언트와 워커 사이에서 메세지를 전달함.</li>
</ul>
<p>이러한 구조에서는 클라이언트와 워커 모두 scaling이 가능하다. 따라서 클라이언트는 불필요하게 무거운 작업으로부터 자유롭고, 워커는 필요에 따라 확장이 가능하다.</p>
<h3 id="celery는-amqp라는-프로토콜을-기반으로-만들어졌다">celery는 AMQP라는 프로토콜을 기반으로 만들어졌다.</h3>
<p>메세지를 보낼때 최소한 한번은 반드시 전달된다!! </p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/0a9a54a1-b886-4707-a846-b37b232ef2c4/image.png" alt=""></p>
<p>Producer가 브로커로 메세지를 보내면, 이 메세지는 Consumer로 전달된다. Consumer가 이 메세지를 Consume한 뒤 브로커로 다 처리했다는 의미의 &quot;Ack&quot;를 보내면, 브로커는 최초에 메세지를 보낸 Producer에게 &quot;Confirm&quot;을 보낸다.</p>
<p>만약 ack에서 문제가 생기면, 브로커는 메세지가 처리됐는지 알 수가 없어서, Consumer에게 메세지를 다시 보내게 됨.</p>
<p>따라서 AMQP 상에서는 이 메세지에 대한 처리를 idempotent하게 되어야 한다.</p>
<pre><code>f(f(x)) = f(x) = y</code></pre><blockquote>
<p>message는 여러번 전달 될 수도 있지만 이때 message 소비는 idempotent 해야한다. 즉, 여러번 전달 되어도 동일하게 하나의 작업만 수행된다는것.(엘레베이터 닫힘 버튼을 여러번 누른다고 여러번 닫히지 않는것과 같음)</p>
</blockquote>
<h2 id="2-안정적으로-완료하기">2. 안정적으로 완료하기</h2>
<h3 id="late-ack">Late Ack&#39;</h3>
<ul>
<li><p>Why Late ACK? : 중요한 태스크를 실행을 해야하는데 실행이 되지 않았을 때.
Ack는 기본적으로 워커가 _<strong>태스크를 실행하기 직전에 실행</strong>_이 된다. 워커에서 Ack&#39;를 브로커로 보내면 큐에서는 해당 작업이 삭제되고 워커에 의해서 작업이 실행된다. 이렇게 실행이 되다가 Worker Crash가 발생하면? <strong>큐에서도 사라져 있으므로, 작업을 다시 실행할 방법이 없다.</strong></p>
</li>
<li><p>Late Ack 를 사용하면?
<em><strong>태스크의 실행이 완료됐을 때 ack가 브로커로 전달된다</strong></em>. 따라서 워커에 의해 태스크가 실행중일 때 worker crash가 발생해도 아직 큐에 남아있으므로 다시 실행할 수 있다.</p>
</li>
</ul>
<ul>
<li>중복 실행될 수 있다!
Late Ack를 쓰면 태스크가 중복 실행될 수 있으므로, 반드시 <em><strong>태스크가 Idempotentic</strong></em> 하게 작성되어야 한다.</li>
</ul>
<h3 id="retry">Retry</h3>
<ul>
<li>예상 가능하지만 통제될 수 없는 상황에서 문제가 생길 경우(외부 API 호출시의 순단 문제 등), 다시 수행되도록함.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/7effd0fd-fe91-47e8-abc0-f0d287f6156a/image.png" alt=""></p>
<ul>
<li>예상 가능한 예외에서만 적용이 되도록 해야하며, atomic하게 작동되어야 한다.</li>
</ul>
<h2 id="3-효율적으로-처리하기">3. 효율적으로 처리하기</h2>
<ul>
<li><p>Worker는 자신이 처리할 수 잇는 만큼만 처리한다. 따라서 처리하는 속도보다 일이 쌓이는 속도가 커도 _*<em>일시적으로는 *</em>_큰 문제가 되지 않는다.</p>
</li>
<li><p>but, 이게 지속이 되면?? 브로커에 불필요한 부하가 가게 되고, 실제 필요한 작업이 진행이 되지 않을수가 있다.</p>
</li>
<li><p>time Limit을 설정해야 태스크가 일정시간 이상 실행되면 종료</p>
</li>
</ul>
<pre><code>&gt; celery -A &lt;PRJ&gt; worker -P &lt;WORKER_POOL_NAME&gt; -c &lt;concurrency&gt;</code></pre><h3 id="prefetch-limit">prefetch limit</h3>
<ul>
<li><p>prefetch limit : ack 되지 않은 태스크의 개수를 worker가 얼마나 갖고 있을 수 있는가?</p>
<blockquote>
<p>prefetch limit = worker_prefetch_multifiler * concurrency </p>
</blockquote>
<p>worker_prefetch_multifiler의 값에 설정한 concurrency 값을 곱하면 이 prefetch limit 값이다.</p>
<p>[worker_prefetch_multifiler = 0]이라면? prefetch limit에 대해 제한이 없으므로 메모리나 효율성을 고려하지 않고 작업을 실행하게 된다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/acc6090e-6f5d-48b9-92dc-393c6e9a337c/image.png" alt=""></p>
<p>  위 그림처럼 concurrency가 각각 1,2인 워커가 있고, message가 쌓여있다고 하자.</p>
<p>  <img src="https://velog.velcdn.com/images/dev_taylor/post/b7930e10-aac2-40d0-86e5-c4d7f8ffbae2/image.png" alt=""></p>
<p>  worker_prefetch_multiflier = 1, acks_late = False로 하게 되면
  concurrency가 1,2 이므로 워커는 각각 prefetch_limit 이 1,2가 된다.</p>
<p>  따라서 concurrency가 2인 워커는 (현재 수행중인 작업을 제외하고) 2개의 메세지를, concurrency가 1인 워커는 1개의 메세지를 prefetch 하게된다.</p>
<ul>
<li><p>그럼 이 prefetch limit을 어떻게 쓸 수 있을까?
<img src="https://velog.velcdn.com/images/dev_taylor/post/516ee32c-2410-4a19-bfa1-39580a543582/image.png" alt=""></p>
<p>긴 태스크에 대해서는 <strong>worker_prefetch_multiplier = 1</strong> 로 설정하면 긴 태스크 뒤에 짧은 태스크들이 불필요하게 실행되는 것을 막을 수 있다.</p>
</li>
<li><p>acks_late = True로 설정하면?</p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/f345fdf9-5912-4ca2-a9ff-650e761d0dcc/image.png" alt=""></p>
<p>worker_prefetch_multiflier = 1이라면 실행중인 task만 prefetch하게 된다.</p>
<p>짧은 태스크들의 경우 worker_prefetch_multiflier에 따라 prefetch를 하는데도 네트워크를 타기 때문에 worker_prefetch_multiflier를 높여주면 task를 더 빠르게 실행시킬 수 있다(????뭔소리야???)</p>
<p>길고 짧은 태스크를 구분하여 워커를 지정하면서 이 옵션을 쓸 수 있다.</p>
</li>
</ul>
<h3 id="prefork">Prefork</h3>
<ul>
<li>multiprocessing으로 구현이 돼있음.</li>
<li>&quot;-c N&quot; 옵션으로 실행을 하면, 1개의 master process와 N개의 child process로 실행이 된다.</li>
<li>master에서 task를 분배하고, 실제 처리는 child에서 이루어진다.</li>
<li>-O fair 옵션 : master에서 child 로 task가 전달될 때 기본적으로는 pipe buffer(한방에 쓸 수 있는 양??)가 허용되는 만큼의 메세지를 전달한다. 하지만 &quot;-O fair&quot; 옵션을 주게 되면 <em><strong>실행 가능한 경우에만</strong></em> 메세지를 전달하게 된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/3fbecc0b-629a-4c60-9410-b7b783c3246e/image.png" alt=""></p>
<p>prefetch_limit은 브로커에서 워커로의 메세지 전달을 통제, -O fair는 마스터 프로세스에서 child process로의 메세지 전달을 통제하는 것이다.</p>
<p>긴 작업과 짧은 작업이 섞여 있는 경우에 -O fair 옵션을 주게 되면 성능 향상 및 예상 가능한 동작을 기대할 수 있다.(한줄서기를 할 때 줄이 더 빨리 줄어드는 것과 같은 원리)</p>
<h3 id="작업의-성질에-따라-적절히-다르게-처리해야-한다">작업의 성질에 따라 적절히 다르게 처리해야 한다.</h3>
<ul>
<li>IO/CPU</li>
<li>중요도</li>
<li>수행시간</li>
<li>실행 빈도</li>
</ul>
<h2 id="4-customization">4. customization</h2>
<h3 id="global-rate-limit">Global Rate Limit</h3>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/6f10685b-c84a-4cab-a789-28655937b171/image.png" alt=""></p>
<p>kombu라는 셀러리가 이용하는 메세징 라이브러리가 있는데, kombu에서 TokenBucket을 이용해서 RateLimit을 구현하고 있다. </p>
<p>TokenBucket?</p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/034e4605-4cc0-473b-b870-eb5a706a92ad/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[celery - Routing Tasks 번역]]></title>
            <link>https://velog.io/@dev_taylor/celery-Routing-Tasks-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@dev_taylor/celery-Routing-Tasks-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Fri, 17 Feb 2023 06:35:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://docs.celeryq.dev/en/stable/userguide/routing.html#id1">공식문서</a>의 Routing tasks를 번역했습니다.</p>
</blockquote>
<p>celery에서는 사용가능한 queue를 task_queues로 세팅한다.</p>
<pre><code>default_exchange = Exchange(&#39;default&#39;, type=&#39;direct&#39;)
media_exchange = Exchange(&#39;media&#39;, type=&#39;direct&#39;)

app.conf.task_queues = (
    Queue(&#39;default&#39;, default_exchange, routing_key=&#39;default&#39;),
    Queue(&#39;videos&#39;, media_exchange, routing_key=&#39;media.video&#39;),
    Queue(&#39;images&#39;, media_exchange, routing_key=&#39;media.image&#39;)
)
app.conf.task_default_queue = &#39;default&#39;
app.conf.task_default_exchange = &#39;default&#39;
app.conf.task_default_routing_key = &#39;default&#39;</code></pre><p>task_default_queue는 명백한 루트가 지정되지 않은 태스크들을 라우팅할 때 쓰인다.</p>
<p>디폴트 exchange, exchange type, routing key는 태스크에 대한 디폴트 라우팅값과, task_queues에 들어가는 디폴트 값으로 쓰인다.</p>
<p>하나의 queue에 대한 여러개의 바인딩도 지원된다. 다음은 같은 queue에 대해 두개의 라우팅 키를 설정한 예시이다.</p>
<pre><code>from kombu import Exchange, Queue, binding

media_exchange = Exchange(&#39;media&#39;, type=&#39;direct&#39;)

CELERY_QUEUES = (
    Queue(&#39;media&#39;, [
        binding(media_exchange, routing_key=&#39;media.video&#39;),
        binding(media_exchange, routing_key=&#39;media.image&#39;),
    ]),
)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[AMQP 입문 - celery 공식문서 번역]]></title>
            <link>https://velog.io/@dev_taylor/AMQP-%EC%9E%85%EB%AC%B8-celery-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@dev_taylor/AMQP-%EC%9E%85%EB%AC%B8-celery-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Fri, 17 Feb 2023 06:35:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>celery의 AMQP <a href="https://docs.celeryq.dev/en/stable/userguide/routing.html#id1">Primer</a> 를 번역했습니다.</p>
</blockquote>
<h3 id="messages">Messages</h3>
<p>message는 header와 body로 구성돼있다. Celery는 header를 message의 content type과 내용의 encoding을 저장하는데 쓴다. content type은 보통 message를 직렬화(serialize)하는데 쓰이는 직렬화 포맷(serialization format)이다. body는 실행될 task의 이름과 task의 id(uuid), task에 적용될 아규먼트들, 그리고 재시도 횟수나 ETA(??)같은 몇개의 추가적인 메타 데이터들을 갖고 있다. </p>
<p>아래는 Python dictionary 타입으로 된 task message 예제이다.</p>
<pre><code>{&#39;task&#39;: &#39;myapp.tasks.add&#39;,
 &#39;id&#39;: &#39;54086c5e-6193-4575-8308-dbab76798756&#39;,
 &#39;args&#39;: [4, 4],
 &#39;kwargs&#39;: {}}</code></pre><h3 id="producers-consumers-and-brockers">Producers, consumers and brockers</h3>
<p>message 발신자를 일반적으로 publisher나 producer라 부르며, message 수신자를 consumer라 한다.</p>
<p>message를 producer에서 consumer로 라우팅 해주는 message server를 broker라 한다.</p>
<h3 id="exchanges-queues-rouing-keys">exchanges, queues, rouing keys</h3>
<ol>
<li>message는 exchange들로 보내어진다.</li>
<li>하나의 exchange가 message들을 하나 혹은 여러개의 queue로 보낸다. 몇개의 exchange 타입이 존재하며 각각 서로 다른 라우팅 방식을 제공하거나 혹은 서로 다른 라우팅 시나리오를 수행한다.</li>
<li>message들은 consume(<em>메세지 수신 혹은 수행</em>)되기 전 까지 queue 내부에서 대기한다. </li>
<li>message는 acknowledge되면 queue에서 삭제된다.<blockquote>
<p>consume은 실제로 message가 수행이 됐을때?아니면 consumer로 수신되었을때?
acknowledge는??</p>
</blockquote>
</li>
</ol>
<p>message가 수신/발신되기 위해서는 아래와 같은 단계를 거친다.</p>
<ol>
<li>exchange 생성</li>
<li>queue 생성</li>
<li>exchange와 queue 바인딩</li>
</ol>
<p>Celery는 task_queues 속에 queue에 필요한 객체를 자동으로 생성해준다(queue의 auto_declare가 False인 경우에는 안됨)</p>
<p>3개의 queue에 대한 예제. 하나는 video를 위한 &#39;video&#39; queue, 하나는 image를 위한 &#39;image&#39; queue, 마지막으로 나머지 다른 모든것을 위한 &#39;default&#39; queue</p>
<pre><code>from kombu import Exchange, Queue

app.conf.task_queues = (
    Queue(&#39;default&#39;, Exchange(&#39;default&#39;), routing_key=&#39;default&#39;),
    Queue(&#39;videos&#39;,  Exchange(&#39;media&#39;),   routing_key=&#39;media.video&#39;),
    Queue(&#39;images&#39;,  Exchange(&#39;media&#39;),   routing_key=&#39;media.image&#39;),
)
app.conf.task_default_queue = &#39;default&#39;
app.conf.task_default_exchange_type = &#39;direct&#39;
app.conf.task_default_routing_key = &#39;default&#39;</code></pre><h3 id="exchange-types">exchange types</h3>
<p>exchange type은 message가 exchange를 통해 queue로 라우팅 되는 방식을 정의한다. 표준적으로 정의된 exchange type은 direct, topic, fanout, headers가 있다. 또한 비표준 exchange타입도 플러그인으로써 Rabbit-MQ에 적용이 가능하다.</p>
<h4 id="direct-exchanges">direct exchanges</h4>
<p>Direct exchanges는 정확한 라우팅 키로 매칭되어, 만약 큐가 &#39;video&#39; 라는 라우팅 키에 바인딩 되어 있을 때, 그 queue는 오직 &#39;video&#39; 라우팅 키를 갖는 messge만 받는다.</p>
<h4 id="topic-exchanges">topic exchanges</h4>
<p>topic exchange는 라우팅 키들을 &quot;.&quot;으로 분리된 단어들과 와일드 카드 문자들(* - 한개의 단어, # - 0 혹은 더 많은 단어들)로 매칭한다.</p>
<p>&quot;usa.news, usa.weather, norway.news, norway.weather&quot; 와 같은 단어들 뿐만 아니라, *.news (모든 뉴스), usa.# (USA에 관련된 모든 것들), usa.weather (모든 USA 날씨) 도 바인딩 될 수 있다.</p>
<h3 id="exchange와-관련된-api-명령들">exchange와 관련된 api 명령들</h3>
<pre><code>exchange.declare(exchange_name, type, passive,
durable, auto_delete, internal)</code></pre><p>exchange 선언 
    - passive :exchange가 생성되지 않음. 또 이를 통해 exchange가 이미 존재하는지의 여부를 확인할 수 있다. 
    - durable : durable(지속적인)한 exchange는 영속적이다. 예를들어, 브로커가 재시작될 때에도 지속된다.   </p>
<ul>
<li>auto_delete : 이 exchange를 사용하는 queue가 더 없을 경우, 이 exchange는 자동으로 삭제된다.</li>
</ul>
<pre><code>queue.declare(queue_name, passive, durable, exclusive, auto_delete)</code></pre><p>queue 선언
    - exclusive : exclusive한 queue는 오직 현재의 커넥션에서만 사용될 수 있다. exclusive 옵션이 설정되면 auto_delete 가 된다.</p>
<pre><code>queue.bind(queue_name, exchange_name, routing_key)</code></pre><p>라우팅 키를 통해 queue와 exchange를 바인딩함.
바인딩되지 않은 queue는 message를 받을 수 없으므로, bind가 반드시 필요하다.</p>
<pre><code>queue.delete(name, if_unused=False, if_empty=False)</code></pre><p>queue와 binding 설정을 삭제함.</p>
<pre><code>exchange.delete(name, if_unused=False)</code></pre><p>exchange 삭제</p>
<blockquote>
<p>선언하는것이 반드시 &quot;생성&quot;을 의미하는것은 아니다. 선언을 할때 이는 객체가 존재하고 그것이 사용 가능하다고 주장하는것이다. 꼭 consumer나 producer가 제일 먼저 exchange/queue/binding을 생성해야 한다는 룰은 없다. 보통 그것들을 필요로 하는 쪽에서 먼저 그것들을 생성한다.</p>
</blockquote>
<h3 id="api-실습hands-on-with-the-api">API 실습(hands-on with the api)</h3>
<p>Celery에는 AMQP API에 대한 CLI 엑세스에 사용되는 celery amqp라는 도구가 함께 제공되어 queue 및 exchange의 생성/삭제, queue제거 또는 message 전송과 같은 관리작업에 엑세스 할 수 있다. AMQP가 아닌 브로커에도 사용 할 수 있지만, 이 경우에는 모든 커맨드의 사용이 제한 될 수 있다.
(redis는 amqp가 아니므로 이건 다음에 기회가 될때 계속 번역함)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Celery - Routing Tasks 번역]]></title>
            <link>https://velog.io/@dev_taylor/Celery-Routing-Tasks</link>
            <guid>https://velog.io/@dev_taylor/Celery-Routing-Tasks</guid>
            <pubDate>Wed, 08 Feb 2023 00:38:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>celery 공식문서의 <a href="https://docs.celeryq.dev/en/stable/userguide/routing.html#redis-message-priorities">Routing Tasks</a>를 번역하였습니다</p>
</blockquote>
<h2 id="1-basic">1. Basic</h2>
<h3 id="자동-라우팅">자동 라우팅</h3>
<p>task를 라우팅하는 가장 쉬운 방법은 task_create_missing_queues 세팅을 사용하는 것이다.</p>
<p>이 세팅을 키면, 아직 task queues에 이름없는 queue가 생성된다. 이를 통해 task라우팅을 손쉽게 수행할 수 있다.</p>
<p>일반적인 task들을 다루는 두개의 서버 x, y와 feed 관련 작업만 다루는 z 서버가 있다 가정하자. 이러한 구성을 사용 할 수 있다.</p>
<pre><code>task_routes = {&#39;feed.tasks.import_feed&#39;: {&#39;queue&#39;: &#39;feeds&#39;}}</code></pre><p>이를 활성화 하면, feed 가져오기 작업(import_feed)은 &quot;feeds&quot; 큐로 라우팅 되고, 다른 모든 작업들은 기본적인 queue로 라우팅된다.</p>
<p>혹은 feed.taks 네임스페이스에 있는 모든 태스크들을 매칭하기 위해, 전역 패턴 매칭 심지어 정규 표현식까지 사용할 수 있다.</p>
<pre><code>app.conf.task_routes = {&#39;feed.tasks.*&#39;: {&#39;queue&#39;: &#39;feeds&#39;}}</code></pre><p>만약 매칭 패턴들의 순서가 중요하다면 items 포맷으로 라우터를 지정해줘야 한다.</p>
<pre><code>task_routes = ([
    (&#39;feed.tasks.*&#39;, {&#39;queue&#39;: &#39;feeds&#39;}),
    (&#39;web.tasks.*&#39;, {&#39;queue&#39;: &#39;web&#39;}),
    (re.compile(r&#39;(video|image)\.tasks\..*&#39;), {&#39;queue&#39;: &#39;media&#39;}),
],)</code></pre><blockquote>
<p>task_routes 세팅은 딕셔너리 타입일수도 있고 리스트 타입일수도 있다. 이련 경우에는, 리스트나 딕셔너리를 튜플로 감싸줘야 한다.</p>
</blockquote>
<p>라우터 설정이 끝난 뒤에, z 서버만을 feeds 큐를 처리하기 위해 구동시킬 수 있다.</p>
<pre><code>user@z:/$ celery -A proj worker -Q feeds</code></pre><p>원하는 만큼 큐를 지정할 수 있어서, 이 서버 프로세스를 아래와 같이 디폴트 큐로 지정할 수 있다.</p>
<pre><code>user@z:/$ celery -A proj worker -Q feeds,celery</code></pre><ul>
<li>디폴트 큐의 이름 변경<pre><code>app.conf.task_default_queue = &#39;default&#39;</code></pre></li>
<li>큐 정의 방법
큐의 이름은 아래와 같이 지정된다.<pre><code>{&#39;exchange&#39;: &#39;video&#39;,
&#39;exchange_type&#39;: &#39;direct&#39;,
&#39;routing_key&#39;: &#39;video&#39;}</code></pre>AMQP가 아닌 REDIS나 SQS는 변경을 지원하지 않으므로, &quot;exchange&quot;와 큐의 이름을 동일하게 해주어야 한다. </li>
</ul>
<h3 id="수동-라우팅">수동 라우팅</h3>
<p>다시 일반적인 task들을 다루는 두개의 서버 x, y와 feed 관련 작업만 다루는 z 서버가 있다 가정하자. 아래와 같은 구성을 사용할 수 있다.</p>
<pre><code>from kombu import Queue

app.conf.task_default_queue = &#39;default&#39;
app.conf.task_queues = (
    Queue(&#39;default&#39;,    routing_key=&#39;task.#&#39;),
    Queue(&#39;feed_tasks&#39;, routing_key=&#39;feed.#&#39;),
)
app.conf.task_default_exchange = &#39;tasks&#39;
app.conf.task_default_exchange_type = &#39;topic&#39;
app.conf.task_default_routing_key = &#39;task.default&#39;</code></pre><p>task_queues는 큐 객체들의 리스트이다. 만약 exchange나 echange_type값을 바꾸지 않았다면, 이 값들은  task_default_exchange와 task_default_exchange_type 세팅에서 가져오게 된다.</p>
<p>태스크를 feed_tasks 큐로 라우팅하기 위해, task_routes 세팅을 추가할 수있다.</p>
<pre><code>task_routes = {
        &#39;feeds.tasks.import_feed&#39;: {
            &#39;queue&#39;: &#39;feed_tasks&#39;,
            &#39;routing_key&#39;: &#39;feed.import&#39;,
        },
}</code></pre><p>또한 routing_key아큐먼트를 사용해서 Task.apply_async() 나  send_task()로 오버라이딩 할 수도 있다.</p>
<pre><code>&gt;&gt;&gt; from feeds.tasks import import_feed
&gt;&gt;&gt; import_feed.apply_async(args=[&#39;http://cnn.com/rss&#39;],
                        queue=&#39;feed_tasks&#39;,
                        routing_key=&#39;feed.import&#39;)</code></pre><p>z서버가 feed queue에서만 처리되도록 하기 위해 셀러리 구동시 celery worker -Q 옵션을 사용한다.</p>
<pre><code>celery -A proj worker -Q feed_tasks --hostname=z@%h</code></pre><p>서버 x와 y는 디폴트 큐를 사용하도록 반드시 표기해주어야 한다.</p>
<pre><code>user@x:/$ celery -A proj worker -Q default --hostname=x@%h
user@y:/$ celery -A proj worker -Q default --hostname=y@%h</code></pre><p>만약 다른 exchange에 있지만 추가하고 싶은 다른 queue가 있다면 exchange와 exchange type을 지정해주기만 하면 된다.</p>
<pre><code>from kombu import Exchange, Queue

app.conf.task_queues = (
    Queue(&#39;feed_tasks&#39;,    routing_key=&#39;feed.#&#39;),
    Queue(&#39;regular_tasks&#39;, routing_key=&#39;task.#&#39;),
    Queue(&#39;image_tasks&#39;,   exchange=Exchange(&#39;mediatasks&#39;, type=&#39;direct&#39;),
                           routing_key=&#39;image.compress&#39;),
)</code></pre><h2 id="2-special-routing-options">2. Special Routing Options</h2>
<h3 id="redis-메세지-우선순위">Redis 메세지 우선순위</h3>
<p>Celerㅛ Redis 전송은 우선순위 필드를 존중(???) 하긴 하지만, 실제로 Redis에는 우선순위 개념이 없다. 따라서 Redis로 우선순위를 구현하기 위해서는 아래의 사항을 참고할 필요가 있다.</p>
<p>우선순위에 따라 작업을 예약하려먼 queue_order_strategy 전송 옵션을 구성해야한다.</p>
<pre><code>app.conf.broker_transport_options = {
    &#39;queue_order_strategy&#39;: &#39;priority&#39;,
}</code></pre><p>우선순위는 각각의 queue에 대해 n개의 리스트를 생성함으로써 지원된다.비록 10개(0~9)의 우선순위 레벨이 있지만, 이 우선순위 레벨들은 자원을 세이브하기 위해 기본적으로 4개 수준으로 통합되었다는 뜻이다(???). 즉, celery라는 큐는 실제로는 4개의 큐로 분할됨을 뜻한다.</p>
<p>celery라 이름붙여진 큐가 가장 우선순위가 높으며, 다른 큐들은 분할자(기본적으로 x06x16)를 갖게 되고, 이 뒤에 우선순위 숫자가 붙게 된다.</p>
<pre><code>[&#39;celery&#39;, &#39;celery\x06\x163&#39;, &#39;celery\x06\x166&#39;, &#39;celery\x06\x169&#39;]

-&gt; celery + \06\16(분할자) + 3 (우선순위) 
</code></pre><p>만약 더 많은 우선순위 레벨이나 다른 분할자를 지정하고자 할 때에는 broker_transport_options에서 &#39;prioty_steps&#39;와 &#39;sep&#39;옵션을 지정할 수 있다.</p>
<pre><code>app.conf.broker_transport_options = {
    &#39;priority_steps&#39;: list(range(10)),
    &#39;sep&#39;: &#39;:&#39;,
    &#39;queue_order_strategy&#39;: &#39;priority&#39;,
}</code></pre><p>이렇게 세팅을 하면 아래와 같은 queue가 만들어진다.</p>
<pre><code>[&#39;celery&#39;, &#39;celery:1&#39;, &#39;celery:2&#39;, &#39;celery:3&#39;, &#39;celery:4&#39;, &#39;celery:5&#39;, &#39;celery:6&#39;, &#39;celery:7&#39;, &#39;celery:8&#39;, &#39;celery:9&#39;]</code></pre><p>이렇게 구현된 우선순위들은 절대 서버 수준에서 구현된 우선순위들만큼 좋을리는 없고, 기껏해야 근사치 정도일 수 있다. 다만, 어플리케이션 수준에서는 충분할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Celery 최적화]]></title>
            <link>https://velog.io/@dev_taylor/Celery-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@dev_taylor/Celery-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Wed, 01 Feb 2023 12:48:52 GMT</pubDate>
            <description><![CDATA[<h3 id="1-ensuring-operation">1. Ensuring Operation</h3>
<p>하나의 시스템이 제 시간에 처리할 수 있는 데이터의 양은 제한되어 있다. 만약 셀러리에서 하나의 태스크가 완료되는데 10분이 걸리고, 매 분마다 새로운 태스크들이 계속해서 유입된다면, 큐는 절대로 비는 일이 없을 것이다. 이것이 큐의 길이를 모니터링 하는 것이 중요한 그 이유이다.</p>
<p> 큐를 모니터링 하는 방법으로는 <a href="https://github.com/etalab/munin-plugins">Munin</a>(현재 큐에 있는 태스크의 수를 그래프로 보여줌)을 사용하는 것이 있다. 만약 큐가 더이상 수용할수 없을 만큼 가득 차게 된다면 즉각적으로 확인할 수 있다. 이를 통해 새로운 worker node를 추가하거나, 불필요한 태스크를 제거하는 등의 조치를 취할 수 있다.</p>
<h3 id="2-일반적인-세팅">2. 일반적인 세팅</h3>
<h4 id="broker-connection-pool">Broker Connection pool</h4>
<p> Broker Connection pool은 2.5 버전부터 기본사양으로써 사용이 가능하다.
 Broker Connection Pool을 미세 조정함으로써 경쟁 상황을 최소화 할 수 있는데, 이 값들은  broker connection을 사용하는 활성화된 thread의 수에 기반해야 한다.</p>
<h4 id="using-transient-queues">Using Transient Queues</h4>
<p>  기본적으로 Celery에 의해 만들어진 queue는 영구적이다. 이 말은, 브로커가 재시작한다 할지라도, 브로커는 태스크가 반드시 수행되도록 message를 디스크에 기록한다는 것이다.</p>
<p>  하지만 어떤 경우에는, 이러한 메시지가 손실되어도 괜찮기 때문에 모든 task가 내구성을 가질 필요는 없다. 이러한 태스크에는 &quot;임시 큐&quot;를 생성하여 사용함으로써 퍼포먼스를 증대 시킬 수 있다.</p>
<pre><code>
from kombu import Exchange, Queue

task_queues = (
    Queue(&#39;celery&#39;, routing_key=&#39;celery&#39;),
    Queue(&#39;transient&#39;, Exchange(&#39;transient&#39;, delivery_mode=1),
          routing_key=&#39;transient&#39;, durable=False),
)</code></pre><p>혹은 task_routes를 사용한다</p>
<pre><code>task_routes = {
    &#39;proj.tasks.add&#39;: {&#39;queue&#39;: &#39;celery&#39;, &#39;delivery_mode&#39;: &#39;transient&#39;}
}</code></pre><p>delery_mode는 queue로의 메세지 전달방식을 바꾼다. 1은 message가 반드시 디스크에 기록 되지 않아도 된다는걸 뜻하며, 2는 반드시 디스크에 기록되어야 함을 의미한다.</p>
<p>queue의 인자를 지정해줌으로써, 태스크를 새로 생성한 큐로 가도록 할 수 있다.</p>
<pre><code>task.apply_async(args, queue=&#39;transient&#39;)
</code></pre><h3 id="3worker-settings">3.Worker Settings</h3>
<h4 id="prefetch-limits">Prefetch Limits</h4>
<p> Prefetch limits는 하나의 worker가 처리할 수 있는 task(message)의 수이다. 만약 prefetch limit이 0일 경우, 작업자는 message를 더 빨리 처리할 수 있는 노드가 있거나 혹은 이 message가 메모리 크기에 맞지 않는 등의 문제를 고려하지 않고 <strong>계속해서 message(task)를 처리</strong>할 것이다.</p>
<p>worker의 기본 prefetch 카운트 수는, <strong>worker_prefetch_multiplier</strong>옵션을 통해 지정할 수 있다(기본값 4)</p>
<p><strong>개별 작업의 처리 시간이 긴 경우 prefetch 수는 1</strong>로 해야 한다. 즉, 한번에 worker process 하나당 하나의 작업만 예약하도록 해야한다.(복잡한 수학 연산등이 이에 해당)</p>
<p>반면, 개별 작업의 처리시간은 짧지만 처리량이 많거나 왕복 대기시간이 긴 경우 prefetch count가 커야 한다. message들이 미리 가져와져서(prefetced) 메모리에 올라가면 worker는 초당 더 많은 작업을 수행할 수 있다. 이 적절한 값을 찾기 위해서는 여러 실험을 해봐야한다...이러한 상황에는 50 이나 150 등이 좋을수도?!</p>
<p>개별 작업의 처리시간이 긴 작업들과 짧은 작업들이 섞여 있을 경우, 가장 좋은 방법은 <strong>2개의 worker node를 사용해</strong>서 긴 작업과 짧은작업으로 나눠서 처리해주는 것이다.<a href="https://docs.celeryq.dev/en/stable/userguide/routing.html#guide-routing">Routing Tasks</a></p>
<h4 id="memory-usage">Memory Usage</h4>
<p>만약 <strong>하나의 worker에서 높은 메모리 점유를 경험</strong>한다면, 가장 먼저 이 이슈가 Celery master process에서도 일어나는지 확인해야 한다. Celery master process의 메모리 점유는 구동 된 이후에 계속해서 극적으로 증가하면 안된다. 이러한 일이 발생한다면 이는 <strong>메모리 누수</strong>일 수 있다.</p>
<p>만약 자식 프로세스에만 메모리 사용량이 높을 경우 해당 task에 문제가 있다는 것이다. </p>
<p>파이썬 프로세스의 메모리 사용에는 &quot;high watermark&quot; 라는 것이 있어 자식 프로세스가 중단되기 전까지는 운영체제에게 메모리를 반환하지 않음을 명심하라. 이말은 즉, 하나의 메모리 사용량이 높은 작업이 있다면 자식프로세스를 재시작 하기 전까지는 영구적으로 자식 프로세스의 메모리 사용을 증가시킬 수 있다는 뜻이다. chunking 로직을 이러한 task에 추가하여 메모리 사용을 줄일 필요가 있다.</p>
<p>Celery worker는  &quot;<a href="https://docs.celeryq.dev/en/stable/userguide/configuration.html#std-setting-worker_max_tasks_per_child">worker_max_tasks_per_child</a>&quot;와 &quot;<a href="https://docs.celeryq.dev/en/stable/userguide/configuration.html#std-setting-worker_max_memory_per_child">worker_max_memory_per_child</a>&quot;을 사용하여 자식 프로세스로부터의 메모리 누수를 줄일 수 있다.</p>
<p>이 세팅들을 너무 낮지 않도록 해야하며, 또한 worker가 자식 프로세스를 재시작 하는데 드는 시간이 task를 처리하는데 드는 시간보다 더 걸리지 않도록 주의해야 한다. 예를 들어 만약 &quot; worker_max_tasks_per_child&quot;의 값을 1로 사용하고, 자식 프로세스가 시작되는데 걸리는 시간이 1초라면, 자식프로세스는 분당 최대 60개의 task만을 처리할 수 있을 것이다. 비슷한 상황은 태스크의 수가 언제나 &quot;worker_max_memory_per_child&quot;보다 클 경우 발생할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[jenkins pipeline]]></title>
            <link>https://velog.io/@dev_taylor/jenkins-pipeline</link>
            <guid>https://velog.io/@dev_taylor/jenkins-pipeline</guid>
            <pubDate>Tue, 03 Jan 2023 13:22:44 GMT</pubDate>
            <description><![CDATA[<h3 id="1-pipeline이란">1. pipeline이란?</h3>
<blockquote>
<p>Jenkins Pipeline (or simply &quot;Pipeline&quot; with a capital &quot;P&quot;) is a suite of plugins which supports implementing and integrating continuous delivery pipelines into Jenkins.
젠킨스 파이프라인은 지속적인 배포 파이프라인을 구현하고, 젠킨스에 통합하는것을 지원하는 플러그인이다.</p>
</blockquote>
<h3 id="2-declarative-vs-scripted-파이프라인-문법">2. Declarative vs Scripted 파이프라인 문법</h3>
<h3 id="3-왜-pipeline을-사용하는가">3. 왜 pipeline을 사용하는가?</h3>
<h3 id="4-pipeline의-컨셉들">4. pipeline의 컨셉들</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[docker 환경에서 jenkins 사용 : jdk 경로 지정]]></title>
            <link>https://velog.io/@dev_taylor/docker-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-jenkins-%EC%82%AC%EC%9A%A9-jdk-%EA%B2%BD%EB%A1%9C-%EC%A7%80%EC%A0%95</link>
            <guid>https://velog.io/@dev_taylor/docker-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-jenkins-%EC%82%AC%EC%9A%A9-jdk-%EA%B2%BD%EB%A1%9C-%EC%A7%80%EC%A0%95</guid>
            <pubDate>Thu, 22 Dec 2022 15:21:44 GMT</pubDate>
            <description><![CDATA[<h2 id="1깔끔하게-jdk11을-쓰는-젠킨스를-먼저-도커에-설치해주자">1.깔끔하게 jdk11을 쓰는 젠킨스를 먼저 도커에 설치해주자</h2>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/3e95639d-9e02-43ec-bd1f-cdd6f84b1672/image.png" alt=""></p>
<h2 id="2-관리자-권한으로-젠킨스에-들어가준다">2. 관리자 권한으로 젠킨스에 들어가준다</h2>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/d29336b1-4ec1-447d-a79b-12665c20bb71/image.png" alt=""></p>
<h2 id="3openjdk-설치">3.openjdk 설치</h2>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/16724af3-594e-40e1-8ffb-8d746da05b20/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/1700069e-9434-4e2c-8bab-688022951932/image.png" alt=""></p>
<h2 id="4-jdk가-설치된-경로를-찾는다env">4. jdk가 설치된 경로를 찾는다(env)</h2>
<p>env를 입력해주면 jenkins 컨테이너의 환경변수가 주르륵 나온다. 여기에서 JAVA_HOME이 해당 경로이다.</p>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/19990efe-41ec-454c-af39-da1ec692e936/image.png" alt=""></p>
<p>JAVA_HOME=/opt/java/openjdk 확인</p>
<h2 id="5-위-경로를-jenkins-web의-jdk-경로로-지정해준다">5. 위 경로를 jenkins-web의 jdk 경로로 지정해준다.</h2>
<p><img src="https://velog.velcdn.com/images/dev_taylor/post/ac4cbf81-62a4-4b34-9c4e-4fc0fd832188/image.png" alt=""></p>
<h2 id="6-빌드여부-확인">6. 빌드여부 확인</h2>
<p>너무 잘된다 ㅎㅋㅎ
<img src="https://velog.velcdn.com/images/dev_taylor/post/cacdae4a-e860-49a0-9359-50d82586c1fd/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[docker에 설치된 jenkins 접속]]></title>
            <link>https://velog.io/@dev_taylor/docker%EC%97%90-%EC%84%A4%EC%B9%98%EB%90%9C-jenkins-%EC%A0%91%EC%86%8D</link>
            <guid>https://velog.io/@dev_taylor/docker%EC%97%90-%EC%84%A4%EC%B9%98%EB%90%9C-jenkins-%EC%A0%91%EC%86%8D</guid>
            <pubDate>Thu, 22 Dec 2022 12:45:17 GMT</pubDate>
            <description><![CDATA[<h3 id="1-도커에서-동작하는-jenkins-확인">1. 도커에서 동작하는 jenkins 확인</h3>
<h4 id=""><img src="https://velog.velcdn.com/images/dev_taylor/post/f1e5ceed-daad-4e6c-993e-f836a0343d9f/image.png" alt=""></h4>
<h3 id="2-docker의-jenkins-컨테이너에-터널링으로-접속">2. Docker의 jenkins 컨테이너에 터널링으로 접속</h3>
<h4 id="--docker-exec--it-jenkins-server-bash">- docker exec -it jenkins-server bash</h4>
<h4 id="-1"><img src="https://velog.velcdn.com/images/dev_taylor/post/964a08e0-52cc-45ea-b819-e0113e8991b7/image.png" alt=""></h4>
<h3 id="3-jenkins-컨테이너-내부에서-내-프로젝트-찾기">3. jenkins 컨테이너 내부에서 내 프로젝트 찾기</h3>
<h4 id="-2"><img src="https://velog.velcdn.com/images/dev_taylor/post/d4dc8711-52db-4378-b33d-870581c592ad/image.png" alt=""></h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[jenkins 기본 개념]]></title>
            <link>https://velog.io/@dev_taylor/jenkins-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@dev_taylor/jenkins-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Thu, 22 Dec 2022 12:02:59 GMT</pubDate>
            <description><![CDATA[<h3 id="1-cici-기본-개념">1. ci/ci 기본 개념</h3>
<pre><code>- continuous integration : 여러 개발자들의 코드베이스를 계속해서 통합함.
- continuous delivery : 서비스를 배달함. 코드 베이스가 항상 배포 가능한 상태로 유지하는 것.
- continuous deployment : 코드베이스를 사용자가 사용가능한 환경으로 배포하는것을 자동화함.

- 코드가 지속적으로 합쳐짐 : 개발자와 사용자 사이의 격차를 없임
- 이러한 과정에는 코드 빌드+테스트+배포 가 포함됨.

- ci/cd가 없으면 코드가 통합이 안돼어 있어서 여러 개발자가 개발한것을 합칠때 문제가 됨.
- 가능한 코드를 만들자마자 바로바로 합치자는 것.

- ci : 가능한 최대한 많이 빨리 내 코드를 코드 베이스에 안착시킴
- cd : </code></pre><h3 id="2-jenkins의-기본-개념과-동작-방식">2. Jenkins의 기본 개념과 동작 방식</h3>
<pre><code>- 이러한 귀찮은것들을 대신해주는것이 젠킨스
- java runtime 위에서 동작한다. 아니면 도커에서 하든가
- 다양한 플러그인을 활용해서 각종 자동화 작업을 처리함. 하나의 플러그인으로 모듈화함.
- 일련의 “자동화 작업의 순서들의 집합”인 파이프라인을 통해 ci/cd 파이프라인을 구축함.
- 두가지의 pipeline syntax가 존재 : declarative, scripted —&gt; 얼리페이에서는 declarative pipeline syntax를 사용함.

- 대표적인 플러그인
    a. Credentials plugin : 젠킨스는 단지 서버임. 배포에 필요한 각종 리소스에 접근하기 위해서는 중요 정보들을 저장해야 한다. 
                    이러한 중요 정보(was token, git access token 등등)을 저장해주는 플러그인.

    b. Pipeline plugin : 파이프라인이란 ci/cd 파이프라인을 젠킨스에 구현하기 위한 “일련의 플러그인의 집합이자 구성”,
                  여러 플러그인들을 이 파이프라인에서 용도에 맞게 사용하고 정의함으로써 파이프라인을 통해 서비스가 배포됨.
                일종의 작업 명세서/레시피.
            1) section : 대 카테고리, 누가 어떤 일을 할 것인가.
                - agent section : 여러 slave node를 두고 일을 시킬 수 있는데, 이처럼 어떤 젠킨스가 일을 하게 할것인지를 지정한다.
                - post section : 각 순서(stage)마다 이후의 결과에 따라 후속 조치를 할 수 있다.(성공시에 성공 이메일 등등)
                - stage section : 어떤 일들을 처리할 것인지 일련의 stage(순서)를 정의함.
                - step section : 한 스테이지 안에서의 단계로, 일련의 스텝을 보여준다.

            2) declarative : 각 스테이지 안에서 어떤 일을 처리할 것인지를 정의함.
                - when : 언제 실행되는가
                - trigger : 어떤 형태로 트리거가 되는가, 이 파이프라인이 어떤 주기로 실행이 되는가
                - environment : 환경 변수

            3) steps : 실행 가능한 여러 작업들, 플러그인을 설치하면 그 플러그인에 맞는 스텝들을 쓸 수 있다.

    c. Docker plugin</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 멀티 스레딩 vs 멀티 프로세싱]]></title>
            <link>https://velog.io/@dev_taylor/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%94%A9-vs-%EB%A9%80%ED%8B%B0-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8B%B1</link>
            <guid>https://velog.io/@dev_taylor/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%94%A9-vs-%EB%A9%80%ED%8B%B0-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8B%B1</guid>
            <pubDate>Sun, 11 Dec 2022 05:03:49 GMT</pubDate>
            <description><![CDATA[<ol>
<li><p>멀티 스레딩의 장점이자 단점 : 메모리를 공유한다</p>
<ul>
<li><p>멀티 프로세싱이라면 : 하나의 파이썬 파일을 자식 프로세스로 복제하여 진행</p>
</li>
<li><p>멀티 스레딩은 하나의 프로세스에서 스레드를 여러개 만들어서 진행 : 이 스레드들끼리 메모리를 공유한다.</p>
</li>
<li><p>개중 하나의 스레드에서 계산을 하다가 에러가 나면 다른 스레드들도 에러가 생길수 있다.</p>
</li>
<li><p>따라서 이 멀티 스레딩으로 병렬 계산을 하는것은 위험 할 수 있음</p>
</li>
<li><p>그래서 GIL(global interpreter lock) 도입</p>
</li>
</ul>
</li>
<li><p>GIL</p>
<ul>
<li><p>한번에 1개의 스레드만 유지하는 락 : 병렬성 x</p>
</li>
<li><p>이 때문에 파이썬에서는 스레드로 병렬성 연산을 수행하지 못함</p>
</li>
<li><p>대신 동시성(concurrency)를 사용해서 io바운드 코드에서 활용 가능</p>
</li>
<li><p>하지만 연산 레벨의 cpu bound에서는 활용이 어려움</p>
</li>
<li><p>이를 보완하는게 멀티 프로세싱</p>
</li>
</ul>
</li>
<li><p>멀티 프로세싱 :</p>
<ul>
<li><p>프로세스들을 공유하고 각 프로세스들 끼리 메모리 공유x, 프로세스들 끼리 통신을 해야함.</p>
</li>
<li><p>멀티 프로세싱을 사용하면 통신 - 직렬화/역직렬화에 대한 비용이 크다.</p>
</li>
<li><p>이러한 비용을 감수하더라도 성능/속도의 이점을 누리고 싶을 때, 멀티 프로세싱 사용.</p>
</li>
<li><p>cpu 집약적인 연산을 할 때에는 : 멀티 프로세싱&gt;동기적 연산 &gt; 멀티 스레딩</p>
</li>
</ul>
</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>