<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>욱이의 IT 생존일지</title>
    <link>https://wookiist.tistory.com/</link>
    <description>Go언어와 Kubernetes를 좋아하는 엔지니어입니다. 
꾸준히 배웁니다.</description>
    <language>ko</language>
    <pubDate>Fri, 21 Aug 2026 00:11:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>wookiist</managingEditor>
    <image>
      <title>욱이의 IT 생존일지</title>
      <url>https://tistory1.daumcdn.net/tistory/1957210/attach/316e5fd9ac4746fdbb677e150ef2856a</url>
      <link>https://wookiist.tistory.com</link>
    </image>
    <item>
      <title>[Wookiist] 글또 7기 활동을 회고해보자</title>
      <link>https://wookiist.tistory.com/177</link>
      <description>&lt;p&gt;벌써 글또 7기 활동이 마무리 되었네요.&lt;br&gt;자취를 시작했던 첫 날 밤, 책상이 아직 도착하지 않아 침대에 앉아 글또 OT에 참석했던 걸 생각해보면.. 꽤 시간이 많이 지난 거 같아요. &lt;/p&gt;
&lt;p&gt;약 반 년이라는 시간동안 저는 무엇을 생각했고, 무엇을 느꼈으며, 어떻게 변했을까요? 글또 7기 활동을 마무리하는 글을 작성해보았습니다.&lt;/p&gt;
&lt;h1&gt;7기 활동하면서 썼던 글&lt;/h1&gt;
&lt;p&gt;7기 활동하면서 작성했던 글의 제목을 나열해보면 이렇네요. 총 10편의 글을 작성했고, 이 중에 7편의 글이 Airflow에 관한 글이었어요. 확실히 이번 활동 기간동안 Airflow 관련해선 꽤 많이 정리한 거 같네요.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wookiist.dev/168&quot;&gt;[Airflow] Airflow DAG Serialization (직렬화)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wookiist.dev/169&quot;&gt;[Airflow] Sensor를 정리해보자&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wookiist.dev/170&quot;&gt;[Airflow] Dynamic Task Mapping (동적 태스크 매핑)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wookiist.dev/171&quot;&gt;[Airflow] Pool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wookiist.dev/172&quot;&gt;[Storage] Block Storage와 File Storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wookiist.dev/173&quot;&gt;[Hadoop] WebHDFS와 HttpFS는 무엇이 다른걸까?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wookiist.dev/174&quot;&gt;[Airflow] PythonSensor에 pod override 옵션 적용하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wookiist.dev/175&quot;&gt;[Airflow] Backfiil과 Clear를 정리해보자&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wookiist.dev/176&quot;&gt;[Airflow] Airflow 2.4.0에선 무엇이 달라졌을까?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wookiist.dev/177&quot;&gt;[Wookiist] 글또 7기 활동을 회고해보자&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;처음 활동을 시작할 때 마음 먹었던 것에 비하면 한참 부족한 양이고 내용이지만, 끝까지 탈주하지 않고 마무리 지었다는 것에 만족합니다 ㅎㅎ &lt;/p&gt;
&lt;p&gt;아쉬운 점은 아직도 제 글이, 튜토리얼이나 트러블슈팅, 번역글에 한정되고 있다는 점이에요. 분명 글또 시작할 땐, “내 생각을 담은 글”을 작성하고 싶다는 다짐을 했는데, 생각이라곤.. 마지막 활동 회고 글이 끝이네요. &lt;/p&gt;
&lt;p&gt;다음 글또 8기 활동에 참여하게 된다면, “hadoop, k8s, streaming app (spark streaming, flink, kafkastream)” 을 주제로 한 글을 많이 작성해볼까 해요. 특히 스트리밍 관련 내용으로 많이 써보려고 합니다. 요새 스트리밍 애플리케이션 코드를 조금 보게 되었는데, 윈도잉, 프로세싱 등의 방법이 굉장히 흥미롭더라구요! 왜 흥미로운지, 어떤 점이 가장 흥미로웠는지 적어보고, 그 점을 어떻게 설명할 것인지 먼저 초안을 작성하는 습관을 들여보려구요.&lt;/p&gt;
&lt;p&gt;겸사겸사 퍼블릭 클라우드로 제공되는 데이터 컴포넌트도 써보면서 이런 내용을 갖고 튜토리얼을 작성해보려고 합니다. &lt;/p&gt;
&lt;h1&gt;변화가 있다면&lt;/h1&gt;
&lt;p&gt;내가 되고 싶은, 하고 싶은 것들을 가득 채운 환경을 만들고, &lt;strong&gt;그 환경에 나를 넣어서 어떻게든 살아남게끔 노력&lt;/strong&gt;해보는 것이 꽤 의미있는 일이라는 생각을 하게 되었어요. 마치 PT 받는 것처럼, 나에게 불편한 환경을 만들어두고, 그 안에서 불편함이 익숙함이 되고, 편안함이 될 때까지 꾸준함을 유지하는게 얼마나 중요한 일인지 알게 되었습니다.  2주마다 새로운 글을 써야 한다는 압박감은 초기에 굉장한 프레셔였어요. 특히 한 6주쯤 흘렀을 때, 초기의 열정과 의지는 어디갔는지.. 다들 잘 쓰는데, 나만 못해서 탈주하게 되면 어떡하지 하는 걱정을 하기도 했어요. 이때 ‘어떤 일을 시작하는 건 나의 열정과 의지이지만, 그걸 이어가도록 도와주는 건 내가 속한 환경이구나’라는 생각을 했던 거 같네요 ㅋㅋ 글또처럼 훌륭한 환경에 나를 맡기고, &lt;strong&gt;어떻게든 탈주하지 않기 위해 아득바득 했던게&lt;/strong&gt; 나름 의미 있던 변화였어요!&lt;/p&gt;
&lt;p&gt;글을 쓰다보니 &lt;strong&gt;독서의 필요성&lt;/strong&gt;을 더 절실히 느낀 것도 변화라면 변화일 거 같아요. 분명 같은 말을 하더라도, 이렇게 풀어서 쓸 필요 없이, 한 단어, 한 문장으로도 충분히 표현이 가능할 거 같은데 그러지 못한 경우가 종종 있었어요. ‘좀 더 풍부한 표현을 할 수 있으려면 나 또한 좋은 글을 많이 보고 사유해봐야 하는구나’ 라는 교훈을 얻었습니다. 이왕이면 8기엔 독서 모임이 있다면 꼭 참가해야겠다고 다짐해봅니다.&lt;/p&gt;
&lt;p&gt;기술적으로도 많이 성장했어요. 알려줄 수 없다면, 가르칠 수 없다면 그 지식은 제 지식이 아니라고들 말 하잖아요? 그게 맞는 말인 것 같습니다. 확실히 누군가에게 소개하기 위해 지식을 정리하다보면 &lt;strong&gt;구름처럼 몽실몽실 떠다니던 정보들이 점차 정돈됨&lt;/strong&gt;을 느낄 수 있었어요. 데이터 엔지니어로써 필요한 소양들에 대해서도 정리할 수 있었고,&lt;/p&gt;
&lt;p&gt;또 피드백이라는 시스템이 들어오면서, “&lt;strong&gt;타인의 글을 꼼꼼히 읽고 의견을 내는&lt;/strong&gt;” 연습을 해볼 수 있었습니다. 보통 이런 시스템이 없다면, 막말로 무지성 글읽기를 할 때가 많았는데요. 이 연습을 통해서 글또 피드백을 위한 글이 아닌 다른 외부 글을 보더라도, 좀 더 천천히 읽어보면서 하나하나 이해를 해가며 읽는 버릇이 &lt;del&gt;조금&lt;/del&gt; 생긴 거 같아요ㅎㅎ&lt;/p&gt;
&lt;h1&gt;좋은 사람들&lt;/h1&gt;
&lt;p&gt;글또 하면서 가장 좋았던 점을 꼽자면, 좋은 사람들을 많이 만났다는 걸 일 순위로 꼽고 싶습니다.&lt;/p&gt;
&lt;p&gt;무엇보다도, ‘하둡 스터디’ 함께 했던 재은님, 병진님, 현주님, 재상님께 많은 감사함을 느낍니다. 꽤나 긴 호흡으로 달려오고 있어서, 조금 나태해지거나 흔들릴 수도 있었을텐데, 그런 일 없이 모두가 마무리를 향해 잘 가고 있다는게 뿌듯하기도 하고, 모두가 으쌰으쌰 열심이라 더 열심히 살아야겠다는 긍정적인 에너지도 많이 받고 있습니다! 새로이 참여하신 진현님도 화이팅!! (스터디가 마무리되는 12월 29일까지 다들 힘내서 달려보자구요!  )&lt;/p&gt;
&lt;p&gt;또 같은 채널의 학건님께도 많은 영감을 받았어요 ㅎㅎ 겪었던 일들을 정리해서 글또콘에서 발표하시는 것을 보고 정말 멋지다고 생각했습니다. 글도 글이지만, 겪은 일을 많은 사람 앞에서 정리해서 이야기하는 것처럼 어려운 건 없다고 생각하는 편이라 더 멋지다고 느꼈던 거 같아요. &lt;/p&gt;
&lt;p&gt;글또를 이끌어주신 성윤님께도 깊은 감사함을 느낍니다. 다양한 방식으로 글또를 발전시키기 위해 노력해주셨음이 잘 . 특히, 코로나 후유증으로 기침을 달고 있으셨을 때에, 제 고민을 듣고 약 1시간 가량의 온라인 피드백 및 멘토링을 해주셨는데 너무 감사했습니다. (가까운 시일 내에 피드백 복기를 위해 간략하게나마 피드백 받았던 내용을 블로그 포스트로 작성해볼까 합니다. )&lt;/p&gt;
&lt;p&gt;마지막으로 글또 분들께도 감사해요ㅎㅎ 헬또 채널에서 매일 운동하시는 분들 보며 자극도 받았고, 힘들 땐 밈또나 멍또 채널을 보며 웃음을 지었습니다. 대나무숲 고민 공유를 올렸을 때 익명이지만, 위로도 받았고 조언도 받을 수 있었다보니 큰 힘이 되었어요. 많은 분이 꾸준히 글을 작성해서 올리고, 서로 피드백하는 이 환경 안에서, 함께 7기 활동을 하신 글또 분들이 있었기에 잘 마무리 할 수 있었다고 생각합니다 ㅎㅎ&lt;/p&gt;
&lt;h1&gt;쓰다보니&lt;/h1&gt;
&lt;p&gt;쓰다보니 어째 수상소감, 아니면 논문 맨 앞 장에 들어가는 ‘감사의 글’을 쓰고 있는 기분이 드네요ㅋㅋㅋ&lt;/p&gt;
&lt;p&gt;그렇지만, 이 활동을 하면서 감사할 일이 이렇게 많았다는 것은, 어쩌면 충분히 긍정적이고, 행복한 시간이었다는 걸 보여주는게 아닐까 합니다. &lt;/p&gt;
&lt;p&gt;사실 정리하기 전까지는 ‘도대체 글또 활동하면서 어떤 변화가 있었던 거지..’ 하는 생각이 들었는데, 글로 적어보니, 반 년동안 활동하면서 나름 많은 생각을 했고, 다양한 자극도 받았으며, 긍정적인 변화도 꽤 있었다는 걸 느낄 수 있었습니다. &lt;/p&gt;
&lt;p&gt;다음 8기에는 “글 작성에 대한 트레이닝” 커리큘럼이 들어갈 수 있다고 해서 더욱 기대중이에요. 아직도 ‘글을 (잘)쓰기’는 쉽지 않고, ‘내 생각을 정리하기’는 더 어렵게 느껴지고 있으니, 이걸 이겨내기 위해서라도 “트레이닝”에 함께 해야겠다는 의지를 불태우기엔 충분한 거 같습니다ㅎㅎ 꼭 ‘글’이 아니더라도 좋은 분들이 많은 그룹에 함께 있는 건 언제나 좋은 일이니 꼭 참여해야겠어요!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“어쨌든.. 7기를 잘 마무리 했으니 8기 활동도 잘 해낼 수 있겠지 뭐~!”&lt;/strong&gt;&lt;/p&gt;</description>
      <category>욱이야기/욱이</category>
      <category>wookiist</category>
      <category>글또</category>
      <category>글또7기</category>
      <category>욱이</category>
      <category>회고</category>
      <author>wookiist</author>
      <guid isPermaLink="true">https://wookiist.tistory.com/177</guid>
      <comments>https://wookiist.tistory.com/177#entry177comment</comments>
      <pubDate>Sat, 15 Oct 2022 12:34:03 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] Airflow 2.4.0에선 무엇이 달라졌을까?</title>
      <link>https://wookiist.tistory.com/176</link>
      <description>&lt;p&gt;본 글은 Ash Berlin-Taylor가 작성한 ‘&lt;a href=&quot;https://airflow.apache.org/blog/airflow-2.4.0/&quot;&gt;Apache Airflow 2.4.0: That Data Aware Release&lt;/a&gt;’ 글을 읽고 번역한 글입니다. 중간 중간에 필요한 제 사견이나 첨언도 들어가 있으니 참고 부탁드립니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Apache Airflow 2.4.0에는 650개 이상의 유저 커밋, 그리고 총 870개 이상의 커밋이 포함되어 있습니다. 이번 버전에는 46개의 새로운 기능, 39개의 개선 사항 그리고 52개의 버그 픽스가 포함됩니다.&lt;/p&gt;
&lt;h2&gt;Data-aware scheduling (AIP-48)&lt;/h2&gt;
&lt;p&gt;정말 대단한 기능입니다. 이제 Airflow는 데이터셋을 업데이트하는 다른 task를 기반으로 DAG을 스케줄링할 수 있게 되었습니다. &lt;/p&gt;
&lt;p&gt;이게 정확히 무슨 뜻일까요? 이 기능으로 인해 DAG 작성자들은 더 작고, 독립적인 DAG을 생성해서, 더 큰 데이터 기반의 워크플로에 연결할 수 있습니다. &lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;자 이야기는 이걸로 충분한 거 같습니다. 이제 &lt;code&gt;my-dataset&lt;/code&gt;이라는 데이터셋을 생성하는 &lt;code&gt;my_task&lt;/code&gt;라는 이름의 간단한 DAG을 생성해봅시다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from airflow import Dataset

**dataset = Dataset(uri=&amp;#39;my-dataset&amp;#39;)**

with DAG(dag_id=&amp;#39;producer&amp;#39;, ...)
    **@task(outlets=[dataset])**
    def my_task():
        ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;데이터셋은 URI로 정의합니다. 이제, 이 데이터셋에 변화가 생기면 언제라도 스케줄되는 두 번째 DAG (&lt;code&gt;consumer&lt;/code&gt;) 을 만들어볼까요.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from airflow import Dataset

**dataset = Dataset(uri=&amp;#39;my-dataset&amp;#39;)**

with DAG(dag_id=&amp;#39;dataset-consumer&amp;#39;, **scheduler=[dataset]**):
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;aside&gt;
  Dataset의 변화는 어떻게 누가 감지하는 걸까요?

&lt;/aside&gt;

&lt;p&gt;&lt;code&gt;my_task&lt;/code&gt; 인스턴스가 완료되면, Airflow는 &lt;code&gt;dataset-consumer&lt;/code&gt;를 실행하는 DAG run을 만들겁니다. &lt;/p&gt;
&lt;p&gt;많은 사람들이 희망하던 dataset 관련 기능을 모두 만족시키지는 못했음을 알고 있습니다.그러나 앞으로 다가올 마이너 릴리즈(2.5, 2.6…)에서 이번 업데이트를 토대로 확장시키고 개선해나갈 예정입니다.&lt;/p&gt;
&lt;p&gt;Dataset 은 데이터셋의 추상화된 컨셉입니다. 이번 릴리즈는 미래를 위한 초석을 심은 것으로, (아직까지는) 데이터셋을 직접 읽고 쓰는 기능은 포함되어 있지 않습니다만, 이 기능은 우리가 목표하는 바의 일부입니다.&lt;/p&gt;
&lt;p&gt;데이터셋에 관련된 더 많은 정보가 필요하다면 ‘&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/2.4.0/concepts/datasets.html&quot;&gt;Data-aware scheduling&lt;/a&gt;’ 문서를 참조해주세요. 이 문서에서 데이터셋이 어떻게 URI로 확인할 수 있는지, 여러 개의 데이터셋에 기반할 수 있는지, 그리고 데이터셋이 무엇인지 생각해볼 수 있을 겁니다.&lt;/p&gt;
&lt;h2&gt;새로운 ExternalPythonOperator를 이용해서 Python 디펜던시 충돌을 더 쉽게 관리하기&lt;/h2&gt;
&lt;p&gt;모든 파이썬 라이브러리가 다같이 행복하게 사용될 수 있기를 바라는만큼, 우리가 사는 세상을 바라보면 &lt;del&gt;(그렇지 못하다보니)&lt;/del&gt; 더 슬퍼집니다. 때론 Airflow 설치 과정에서 여러 개의 파이썬 라이브러리를 설치하고자 할 때 충돌이 발생하곤 하는데요, 최근 들어 &lt;code&gt;dbt-core&lt;/code&gt; 에 관해 이 이슈가 자주 들리고 있습니다.&lt;/p&gt;
&lt;p&gt;이런 문제를 더 쉽게 해결하고자 &lt;code&gt;@task.external_python&lt;/code&gt; (== &lt;code&gt;ExternalPythonOperator&lt;/code&gt;) 를 도입했습니다. 이를 이용하면 Airflow task로 파이썬 함수를 실행할 때, 사전에 정의해둔 virtualenv 내에서 실행하거나, 완전히 다른 파이썬 버전을 이용해 실행할 수 있습니다. 예를 들어볼까요?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@task.external_python(python=&amp;#39;/opt/venvs/task_deps/bin/python&amp;#39;)
def my_task(data_interval_start, data_interval_end):
    print(f&amp;#39;Looking at data between {data_interval_start} and {data_interval_end}&amp;#39;)
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;접근하려는 context_variables에 따라 virtualenv에 설치해야 하는 몇 가지 요소들이 있으니 사용 전에 꼭 ‘&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/2.4.0/howto/operator/python.html#externalpythonoperator&quot;&gt;how-to on using the ExternalPythonOperator&lt;/a&gt;’ 문서를 읽어봐주세요.&lt;/p&gt;
&lt;h2&gt;Dynamic Task Mapping에 관한 더 많은 발전 사항&lt;/h2&gt;
&lt;p&gt;여러분들의 요청사항을 잘 들었습니다. Dynamic Task Mapping은 이제 다음 사항을 지원합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;expand_kwargs&lt;/code&gt; : TaskFlow가 아닌 오퍼레이터에 여러 개의 파라미터를 할당할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zip&lt;/code&gt; : 벡터곱 없이 여러 가지를 합치고 싶을 때 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map&lt;/code&gt; : task가 실행되기 전에 파리미터를 변형하고자 할 때 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;더 많은 dynamic task mapping 관련 정보를 얻고 싶다면, ‘&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/2.4.0/concepts/dynamic-task-mapping.html#transforming-mapped-data&quot;&gt;Transforming Mapped Data, Combining upstream data (aka “zipping”)&lt;/a&gt;’ 문서와 ‘&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/2.4.0/concepts/dynamic-task-mapping.html#assigning-multiple-parameters-to-a-non-taskflow-operator&quot;&gt;Assigning multiple parameters to a non-TaskFlow operator’ 문서를 읽어봐주세요&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;context manager에서 DAG이 자동 등록됩니다 (더 이상 &lt;code&gt;as dag:&lt;/code&gt; 가 필요하지 않아요)&lt;/h2&gt;
&lt;p&gt;이 업데이트는 삶의 질을 조금 높여줄 것입니다. 그리고 필자 입장에선 &lt;code&gt;as dag:&lt;/code&gt; 를 쓰는 것을 얼마나 많이 잊어버렸었는지 인정하고 싶지 않습니다. &lt;/p&gt;
&lt;p&gt;기존의 코드는 다음과 같았죠.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with DAG(dag_id=&amp;#39;example&amp;#39;) as dag:
    ...

@dag
def dag_maker():
    ...

dag2 = dag_maker()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이젠 이렇게만 해도 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with DAG(dag_id=&amp;#39;example&amp;#39;):
    ...

@dag
def my_dag():
    ...

my_dag()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 어떤 이유에선가 DAG이 자동 등록되는 것을 방지하고 싶다면, &lt;code&gt;auto_register=False&lt;/code&gt; 옵션을 넣어주면 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# variable로 할당되지 않았기 때문에, 이 dag은 Airflow에 의해 픽업되지 않을 겁니다.
with DAG(dag_id=&amp;#39;example&amp;#39;, auto_register=False):
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;기타 개선사항들&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/2.4.0/release_notes.html#airflow-2-4-0-2022-09-19&quot;&gt;650개의 커밋 내용&lt;/a&gt;을 여기 포함하기엔 너무 많습니다. 하지만 몇 가지 눈에 띄는 피처를 좀 더 살펴볼까요?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;홈페이지에서 자동 새로고침 기능&lt;/li&gt;
&lt;li&gt;TaskFlow 데코레이터로 &lt;code&gt;@task.short_circuit&lt;/code&gt; 추가&lt;/li&gt;
&lt;li&gt;CLI로 role delete 기능 추가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ExternalTaskSensor&lt;/code&gt; 에서 &lt;code&gt;TaskGroup&lt;/code&gt; 지원&lt;/li&gt;
&lt;li&gt;TaskFlow 데코레이터로 &lt;code&gt;@task.kubernetes&lt;/code&gt; 추가 (오…)&lt;/li&gt;
&lt;li&gt;워커에서 Dynamic DAG의 최적화를 처리하도록 하는 &lt;code&gt;parsing_context&lt;/code&gt; 실험적 기능 추가&lt;/li&gt;
&lt;li&gt;하나의 &lt;code&gt;schedule&lt;/code&gt; 변수로 통합&lt;/li&gt;
&lt;li&gt;이전엔 ‘모 아니면 도’였던 컨피그 마스킹 기능을 이제 Admin → Configuration에서 민감하지 않은 데이터로 설정할 수 있도록 기능 제공&lt;/li&gt;
&lt;li&gt;class 이름에서 operator 이름을 분리(더 이상 TaskFlow를 사용할 때 &lt;code&gt;_PythonDecoratedOperator&lt;/code&gt; 를 이용하지 않아도 됨!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;제 개인적인 생각으론 데이터 리니지에 대한 관리도 편해졌지만, VirtualEnv를 이용해서, 새로운 이미지를 말 필요 없이 한 이미지에 모든 라이브러리를 넣어 둘 수 있다는 것도 큰 장점이라고 보입니다. 물론 이미지의 크기가 너무 커지면, 새로운 노드에서 실행될 때 시간을 많이 잡아 먹겠지만, 좀 더 생각해보면 좋은 활용 방법이 있을 거 같네요. 어서 빌드해서 테스트해봐야겠습니다~!&lt;/p&gt;
&lt;p&gt;+) 헉 그나저나 벌써 2.4.1 이 출시되었네요. 버그가 많았나.. ㅎㅎ&lt;/p&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;p&gt;만약 이 글이 도움이 되셨다면 글 좌측 하단의 하트❤를 눌러주시면 감사하겠습니다.&lt;/p&gt;
&lt;p&gt;혹시라도 글에 이상이 있거나, 오역, 이상한 번역이 있거나, 이해가 가지 않으시는 부분, 또는 추가적으로 궁금하신 내용이 있다면 주저 마시고 댓글 을 남겨주세요! 빠른 시간 안에 답변을 드리겠습니다  &lt;/p&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/blog/airflow-2.4.0/&quot;&gt;Apache Airflow 2.4.0: That Data Aware Release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/2.4.0/concepts/dynamic-task-mapping.html#assigning-multiple-parameters-to-a-non-taskflow-operator&quot;&gt;Dynamic Task Mapping Introduction&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Airflow</category>
      <category>Airflow</category>
      <category>Data Engineering</category>
      <category>데이터 엔지니어</category>
      <category>에어플로</category>
      <author>wookiist</author>
      <guid isPermaLink="true">https://wookiist.tistory.com/176</guid>
      <comments>https://wookiist.tistory.com/176#entry176comment</comments>
      <pubDate>Sat, 15 Oct 2022 12:09:58 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] Backfill과 Clear를 정리해보자</title>
      <link>https://wookiist.tistory.com/175</link>
      <description>&lt;h1&gt;Backfill&lt;/h1&gt;
&lt;p&gt;데이터 파이프라인을 운용하다보면, 이미 지난 날짜를 기준으로 재처리를 해야 하는 일이 왕왕 있습니다. 백필은 바로 이 재처리 작업을 의미합니다. 단어의 의미 그대로 ‘메우는 작업&amp;#39;이라고 생각하면 될 듯합니다.&lt;/p&gt;
&lt;h1&gt;Backfill을 하게 되는 경우&lt;/h1&gt;
&lt;p&gt;백필 작업을 하는 경우는 나름 명확합니다. 다음과 같은 사례가 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;버그가 있거나 어떤 이유로 로직이 변경되었을 때 전체 데이터를 새로 말아주어야 할 때&lt;/li&gt;
&lt;li&gt;컬럼 등의 메타 데이터가 변경되었을 때 이를 반영하기 위한 append 성의 작업이 필요할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이외에도 과거의 데이터를 재처리하고자 하는 니즈가 있다면 백필을 먼저 떠올리면 됩니다.&lt;/p&gt;
&lt;h1&gt;Airflow Backfill&lt;/h1&gt;
&lt;p&gt;Airflow를 이용하고 있다면 몇 가지 방법으로 백필 작업을 수행할 수 있습니다. &lt;/p&gt;
&lt;h2&gt;Backfill&lt;/h2&gt;
&lt;p&gt;Airflow에선 Backfill 커맨드를 제공하고 있습니다. 현재는 CLI로만 이 커맨드를 제공하고 있으며 Web UI에선 사용할 수 없습니다. (별도 플러그인을 제작해서 붙인 게 아니라면요)&lt;/p&gt;
&lt;p&gt;Airflow의 Backfill 커맨드를 알아보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;usage: airflow dags backfill [-h] [-c CONF] [--continue-on-failures] [--delay-on-limit DELAY_ON_LIMIT] [-x] [-n] [-e END_DATE] [-i] [-I] [-l] [-m] [--pool POOL] [--rerun-failed-tasks] [--reset-dagruns] [-B]
                             [-s START_DATE] [-S SUBDIR] [-t TASK_REGEX] [-v] [-y]
                             dag_id

Run subsections of a DAG for a specified date range. If reset_dag_run option is used, backfill will first prompt users whether airflow should clear all the previous dag_run and task_instances within the backfill date range. If rerun_failed_tasks is used, backfill will auto re-run the previous failed task instances  within the backfill date range

positional arguments:
  dag_id                The id of the dag

optional arguments:
  -h, --help            show this help message and exit
  -c CONF, --conf CONF  JSON string that gets pickled into the DagRun&amp;#39;s conf attribute
  --continue-on-failures
                        if set, the backfill will keep going even if some of the tasks failed
  --delay-on-limit DELAY_ON_LIMIT
                        Amount of time in seconds to wait when the limit on maximum active dag runs (max_active_runs) has been reached before trying to execute a dag run again
  -x, --donot-pickle    Do not attempt to pickle the DAG object to send over to the workers, just tell the workers to run their version of the code
  -n, --dry-run         Perform a dry run for each task. Only renders Template Fields for each task, nothing else
  -e END_DATE, --end-date END_DATE
                        Override end_date YYYY-MM-DD
  -i, --ignore-dependencies
                        Skip upstream tasks, run only the tasks matching the regexp. Only works in conjunction with task_regex
  -I, --ignore-first-depends-on-past
                        Ignores depends_on_past dependencies for the first set of tasks only (subsequent executions in the backfill DO respect depends_on_past)
  -l, --local           Run the task using the LocalExecutor
  -m, --mark-success    Mark jobs as succeeded without running them
  --pool POOL           Resource pool to use
  --rerun-failed-tasks  if set, the backfill will auto-rerun all the failed tasks for the backfill date range instead of throwing exceptions
  --reset-dagruns       if set, the backfill will delete existing backfill-related DAG runs and start anew with fresh, running DAG runs
  -B, --run-backwards   if set, the backfill will run tasks from the most recent day first.  if there are tasks that depend_on_past this option will throw an exception
  -s START_DATE, --start-date START_DATE
                        Override start_date YYYY-MM-DD
  -S SUBDIR, --subdir SUBDIR
                        File location or directory from which to look for the dag. Defaults to &amp;#39;[AIRFLOW_HOME]/dags&amp;#39; where [AIRFLOW_HOME] is the value you set for &amp;#39;AIRFLOW_HOME&amp;#39; config you set in &amp;#39;airflow.cfg&amp;#39;
  -t TASK_REGEX, --task-regex TASK_REGEX
                        The regex to filter specific task_ids to backfill (optional)
  -v, --verbose         Make logging output more verbose
  -y, --yes             Do not prompt to confirm. Use with care!&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-c CONF, --conf CONF&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;백필 작업을 수행할 때, 해당 DagRun에 넘겨주어야 하는 CONF를 JSON String 형태로 제공해줄 때 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--continue-on-failures&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;몇몇 Task가 실패하더라도 Backfill 자체는 실패하지 않고 계속 진행되도록 할 때 주는 옵션입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;—delay-on-limit DELAY_ON_LIMIT&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;DagRun을 다시 수행하려고 할 때,  &lt;code&gt;max_active_runs&lt;/code&gt; 값에 막혀 실행되지 못한 경우에 재수행까지 얼마나 기다리도록 할지 지연시간 값을 주는 옵션입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-x, --donot-pickle&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;수행할 DAG를 Worker에 넘겨줄 때 피클링하지 않고, Worker가 갖고 있는 코드를 그대로 수행하도록 하고자 할 때 사용합니다. (언제 쓸 수 있을까요? 흠..)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-e END_DATE, --end-date END_DATE&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;백필 하려는 마지막 DagRun의 &lt;strong&gt;data_interval 시작 시각&lt;/strong&gt;을 의미합니다.&lt;/li&gt;
&lt;li&gt;YYYY-MM-DD 형식을 지원하며, 만약 시간을 붙이고 싶다면 &lt;code&gt;YYYY-MM-DDTHH:mm:SS&lt;/code&gt; 형식으로 나타낼 수 있습니다.&lt;/li&gt;
&lt;li&gt;timezone을 추가하고 싶다면 &lt;code&gt;+&lt;/code&gt; 로 나타낼 수 있습니다. 예를 들어 KST는 다음처럼 표현합니다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2022-09-01T00:10:00+09:00&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-i, --ignore-dependencies&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;기본적으로 상위 Task가 있는 경우, 상위 Task에 종속성이 있다고 판단해서 먼저 실행하는데, 관리자가 판단하기에 종속성을 무시해도 괜찮은 경우, 상위 Task를 실행하지 않게 하는 옵션입다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-I, --ignore-first-depends-on-past&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;첫 번째 Task에 대해서 &lt;code&gt;depends_on_past&lt;/code&gt; 의존성을 무시하도록 하게 합니다. (다만, 첫 번째 이후의 Task는 &lt;code&gt;depends_on_past&lt;/code&gt; 에 의존성이 있습니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-l, --local&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;백필 작업을 LocalExecutor에서 수행합니다. 다른 Executor가 아닌 현재 Backfill 작업을 시작한 위치에서 작업이 수행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-m, --mark-success&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;실제 백필 작업을 수행하는 대신, 모든 Task를 일괄 &lt;code&gt;SUCCESS&lt;/code&gt; 상태로 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--pool POOL&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;백필 Task가 실행될 pool을 정해줄 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--rerun-failed-tasks&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;백필 작업중에 실패한 Task가 있더라도, Exception을 던지는 것이 아니라, 재수행하도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--reset-dagruns&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;DagRun을 초기화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-B, --run-backwards&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;이 옵션을 주면, 가장 최근의 Data_Interval 부터 실행합니다. &lt;code&gt;END_DATE&lt;/code&gt; 부터 &lt;code&gt;START_DATE&lt;/code&gt; 로 흘러갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-s START_DATE, --start-date START_DATE&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;백필 하려는 시작 DagRun의 &lt;strong&gt;data_interval 시작 시각&lt;/strong&gt;을 의미합니다.&lt;/li&gt;
&lt;li&gt;END_DATE 파라미터와 마찬가지로 YYYY-MM-DD 형식을 지원하며, 만약 시간을 붙이고 싶다면 &lt;code&gt;YYYY-MM-DDTHH:mm:SS&lt;/code&gt; 형식으로 나타낼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-t TASK_REGEX, --task-regex TASK_REGEX&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;DAG 내의 특정 Task만 백필하고자 할 때 사용합니다. 지정해주지 않을 경우, DAG 내의 전체 Task가 백필됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어 &lt;code&gt;some_dag&lt;/code&gt; 의 &lt;code&gt;some_task&lt;/code&gt; 를  &lt;code&gt;2022-09-01T00:10:00&lt;/code&gt; 부터 &lt;code&gt;2022-09-01T12:10:00&lt;/code&gt; 까지 백필하고 싶다면 다음처럼 실행하면 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ airflow dags backfill -t some_task some_dag -s 2022-09-01T00:10:00 -e 2022-09-01T12:10:00&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;버그&lt;/h3&gt;
&lt;p&gt;다만 현재 Airflow backfill 커맨드에는 버그가 있습니다. 백필 작업이 시작되면 현재 실행중인 DagRun의 모든 Task Instance를 &lt;code&gt;Scheduled&lt;/code&gt; 상태로 설정하는데, 이게 &lt;code&gt;dagrun.update_state&lt;/code&gt; 를 매번 호출하는데 &lt;code&gt;RUNNING&lt;/code&gt; 상태이거나 &lt;code&gt;Schedulable&lt;/code&gt; 상태인 Task Instance가 없을 때 Deadlock 상태에 빠집니다. &lt;/p&gt;
&lt;p&gt;요약하자면, 실행할 Task Instance를 전부 예약을 걸어두었는데, 추가 예약이 없는 경우 더 진행되지 않고 예약 상태로만 남아있는 문제입니다. &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/apache/airflow/pull/26161&quot;&gt;2.4.0RC1에서 이를 패치한 코드&lt;/a&gt;가 있어서 체리픽해보았는데, 처음엔 잘 되는 거 같아 실제 클러스터에도 적용해봤는데 잘 안 되더군요   백필 코드만 바꾼게 아닐 수도 있을 거 같아서, 실제 2.4.0 버전이 릴리즈되면 그때 다시 적용해보려고 합니다. &lt;/p&gt;
&lt;h2&gt;Clear&lt;/h2&gt;
&lt;p&gt;Backfill과 더불어서 Clear도 백필 작업처럼 사용할 수 있습니다. Clear는 기존에 실행되었던 DagRun을 지워주는 역할을 합니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;usage: airflow tasks clear [-h] [-R] [-d] [-e END_DATE] [-X] [-x] [-f] [-r] [-s START_DATE] [-S SUBDIR] [-t TASK_REGEX] [-u] [-y] dag_id

Clear a set of task instance, as if they never ran

positional arguments:
  dag_id                The id of the dag

optional arguments:
  -h, --help            show this help message and exit
  -R, --dag-regex       Search dag_id as regex instead of exact string
  -d, --downstream      Include downstream tasks
  -e END_DATE, --end-date END_DATE
                        Override end_date YYYY-MM-DD
  -X, --exclude-parentdag
                        Exclude ParentDAGS if the task cleared is a part of a SubDAG
  -x, --exclude-subdags
                        Exclude subdags
  -f, --only-failed     Only failed jobs
  -r, --only-running    Only running jobs
  -s START_DATE, --start-date START_DATE
                        Override start_date YYYY-MM-DD
  -S SUBDIR, --subdir SUBDIR
                        File location or directory from which to look for the dag. Defaults to &amp;#39;[AIRFLOW_HOME]/dags&amp;#39; where [AIRFLOW_HOME] is the value you set for &amp;#39;AIRFLOW_HOME&amp;#39; config you set in &amp;#39;airflow.cfg&amp;#39;
  -t TASK_REGEX, --task-regex TASK_REGEX
                        The regex to filter specific task_ids to backfill (optional)
  -u, --upstream        Include upstream tasks
  -y, --yes             Do not prompt to confirm. Use with care!&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-R, --dag-regex&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;DAG의 이름을 정확하게 지정하지 않는 대신, DAG_ID를 regex를 이용해 검색할 수 있게 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d, --downstream&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;클리어 대상이 되는 태스크의 하위 태스크(현재 태스크에 의존성을 갖고 있는 태스크)를 포함해서 클리어합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-e END_DATE, --end-date END_DATE&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;Clear 할 마지막 DagRun의 &lt;strong&gt;data_interval 시작 시각&lt;/strong&gt;을 의미합니다.&lt;/li&gt;
&lt;li&gt;YYYY-MM-DD 형식을 지원하며, 만약 시간을 붙이고 싶다면 &lt;code&gt;YYYY-MM-DDTHH:mm:SS&lt;/code&gt; 형식으로 나타낼 수 있습니다.&lt;/li&gt;
&lt;li&gt;timezone을 추가하고 싶다면 &lt;code&gt;+&lt;/code&gt; 로 나타낼 수 있습니다. 예를 들어 KST는 다음처럼 표현합니다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2022-09-01T00:10:00+09:00&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-X, --exclude-parentdag&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;Clear 된 Task가 어떤 SubDAG의 일부일 때 ParentDAG을 Clear 대상에 포함하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-x, --exclude-subdags&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;SubDAG을 포함하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-f, --only-failed&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;FAILED 상태의 작업에 대해서만 Clear를 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-r, --only-running&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;RUNNING 상태의 작업에 대해서만 Clear를 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-s START_DATE, --start-date START_DATE&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;Clear 할 시작 DagRun의 &lt;strong&gt;data_interval 시작 시각&lt;/strong&gt;을 의미합니다.&lt;/li&gt;
&lt;li&gt;END_DATE 파라미터와 마찬가지로 YYYY-MM-DD 형식을 지원하며, 만약 시간을 붙이고 싶다면 &lt;code&gt;YYYY-MM-DDTHH:mm:SS&lt;/code&gt; 형식으로 나타낼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-S SUBDIR, --subdir SUBDIR&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;DAG 파일이 위치한 디렉토리를 지정해줄 수 있습니다. 기본값으로 &lt;code&gt;[AIRFLOW_HOME]/dags&lt;/code&gt; 디렉토리가 참조됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-t TASK_REGEX, --task-regex TASK_REGEX&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;DAG 내의 특정 Task에 대해서만 Clear하고자 하는 경우 지정해주면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-u, --upstream&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;클리어 대상이 되는 태스크의 상위 태스크(현재 태스크가 의존성을 갖고 있는 태스크)를 포함해서 클리어합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Backfill 커맨드와 같은 상황을 Clear 커맨드로 나타내보면 다음과 같습니다. (기본값만 나타내보면 크게 다르지 않습니다)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ airflow tasks clear -t some_task some_dag -s 2022-09-01T00:10:00 -e 2022-09-01T12:10:00&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/wookiist/tistory-images/76b0d4fb046f0ef494756032b6e706dbac42446b/2022/09/18/clear_webui.png&quot; alt=&quot;https://raw.githubusercontent.com/wookiist/tistory-images/76b0d4fb046f0ef494756032b6e706dbac42446b/2022/09/18/clear_webui.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Backfill 커맨드와 다르게 Web UI에서도 수행할 수 있습니다. 그리고 스케줄에 따라 스케줄러가 생성했던 DagRun이라면 Clear 되었을 때 자동으로 다시 Trigger 됩니다. &lt;/p&gt;
&lt;p&gt;다만 Backfill 작업이 진행되었던 DagRun에 대해선 Clear 하더라도 다시 트리거가 되지 않는다는 점에 유의해야 합니다. (&lt;a href=&quot;https://stackoverflow.com/questions/55725626/airflow-cleared-backfill-tasks-do-not-get-picked-by-the-scheduler&quot;&gt;참고&lt;/a&gt;) Airflow 스케줄러는 수동으로 생성된 DagRun에 대해선 스케줄 큐에 추가하지 않기 때문입니다. &lt;/p&gt;
&lt;p&gt;또한 이미 실행되었던 DagRun이 아니라면 &lt;del&gt;당연하게도&lt;/del&gt; Clear할 수 없습니다. 따라서 기록되지 않은 시각을 Trigger 하려면 Backfill을 사용하거나 날짜를 직접 주고 실행하는 Trigger Run을 이용해야 합니다. (&lt;code&gt;Trigger DAG w/ config&lt;/code&gt;)&lt;/p&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;p&gt;그러나 백필의 핵심은 이런 기술적인 방법이 아니라, 보다 섬세한 로직과 쿼리에 있는 게 아닐까 합니다. 보통 백필을 하게 되면, 몇 개월 이상의 데이터를 백필하게 되는데, 이 경우 데이터를 읽고 처리하는 과정에서 오랜 시간이 소요됩니다. 데이터를 Write 하는 시간보다는 Read와 Process하는 쪽에서 대부분의 지연이 발생하기 때문입니다.&lt;/p&gt;
&lt;p&gt;예를 들어, STRING 포맷으로 저장된 JSON 데이터를 처리해야 하는 경우, STRING 데이터를 읽어다가 JSON 오브젝트로 serialize 하는 과정이 수반됩니다. 로직을 적용하기엔 STRING 데이터만으로는 어렵기 때문입니다. 이러한 이유로 JSON 오브젝트로의 변환이 이뤄지는데, 이 과정에서 발생하는 자원 소모도 크고, 모든 STRING 데이터를 변환하는 데에 드는 시간도 무시할 수 없습니다. &lt;/p&gt;
&lt;p&gt;따라서 재처리해야 하는 데이터를 최소화하고, 꼭 필요한 데이터에만 로직을 적용하려는 노력이 필요합니다. 잘 짠 로직도 중요하지만, 읽어야 할 데이터 자체가 적다면 그만큼 처리에 필요한 시간도 비약적으로 줄어들테니까요.&lt;/p&gt;
&lt;p&gt;만약 이 글이 도움이 되셨다면 글 좌측 하단의 하트❤를 눌러주시면 감사하겠습니다.&lt;/p&gt;
&lt;p&gt;혹시라도 글에 이상이 있거나, 오역, 이상한 번역이 있거나, 이해가 가지 않으시는 부분, 또는 추가적으로 궁금하신 내용이 있다면 주저 마시고 댓글 을 남겨주세요! 빠른 시간 안에 답변을 드리겠습니다  &lt;/p&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;백필 관련된 정보를 세세히 작성해주신 포스트&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sheerheart.tistory.com/entry/Apache-Airflow-%EC%9E%AC%EC%88%98%ED%96%89-%EB%B0%A9%EB%B2%95-%EC%A0%95%EB%A6%AC&quot;&gt;https://sheerheart.tistory.com/entry/Apache-Airflow-재수행-방법-정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;backfill 관련 이슈를 해결한 패치&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apache/airflow/pull/26161&quot;&gt;https://github.com/apache/airflow/pull/26161&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Airflow</category>
      <category>Airflow</category>
      <category>Backfill</category>
      <category>data</category>
      <category>De</category>
      <category>pipeline</category>
      <category>데이터</category>
      <category>백필</category>
      <category>에어플로</category>
      <category>파이프라인</category>
      <author>wookiist</author>
      <guid isPermaLink="true">https://wookiist.tistory.com/175</guid>
      <comments>https://wookiist.tistory.com/175#entry175comment</comments>
      <pubDate>Sun, 18 Sep 2022 12:14:17 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] PythonSensor에 pod override 옵션 적용하기</title>
      <link>https://wookiist.tistory.com/174</link>
      <description>&lt;h1&gt;PythonSensor &amp;amp; Kubernetes Excutor&lt;/h1&gt;
&lt;p&gt;최근에 Kubernetes Executor를 사용할 때 PythonSensor의 pod_override 옵션을 어떻게 적용해야 하나 하는 문제로 헤맸던 적이 있습니다. 이에 대해 간단하게 정리해보았는데요.&lt;/p&gt;
&lt;p&gt;PythonOperator 문서를 가보면, pod spec을 변경하는 방법을 소개한 문서가 존재합니다. 거기다 이렇게 executor_config를 변경한 &lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/2.0.0/_modules/airflow/example_dags/example_kubernetes_executor_config.html&quot;&gt;예제 코&lt;/a&gt;드도 있습니다.&lt;/p&gt;
&lt;p&gt;문제는 PythonSensor 스펙을 소개한 문서 어딜 봐도 PythonOperator가 제공하는 &lt;code&gt;executor_config&lt;/code&gt;가 없었습니다. 특히 PythonOperator가 상속한 BaseOperator를 보면 &lt;code&gt;executor_config&lt;/code&gt; 필드가 있었지만, BaseSensor에는 없었습니다.&lt;/p&gt;
&lt;p&gt;BaseOperator는 executor_config에 대해 &lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/_api/airflow/models/baseoperator/index.html&quot;&gt;다음의 문서&lt;/a&gt;를 통해 알아볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;그러나 BaseSensor는 executor_config 필드가 따로 정의된 바가 없어서 어지러웠습니다. 그럼에도 Sensor도 결국 Operator와는 본질적으로 같은 오브젝트이니, 써보면 되지 않을까 해서 써봤는데, 이게 웬걸! 잘 생성되는 걸 확인할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;다음은 PythonSensor를 커스텀하는 과정에서 executor_config 필드를 이용해 pod 명세를 정의한 코드입니다. 코드를 보면 kubernetes 오브젝트 기술 방법을 사용해서 pod 디스크립션을 정의한 것을 알 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;K8S_VOLUMES=[
    k8s.V1Volume(
        name=&amp;#39;some_volume&amp;#39;,
        persistent_volume_claim=k8s.V1PersistentVolumeClaimVolumeSource(claim_name=&amp;#39;some_volume&amp;#39;),
    )
]

K8S_VOLUME_MOUNTS=[
    k8s.V1VolumeMount(name=&amp;#39;some_volume&amp;#39;, mount_path=&amp;#39;/some_path/&amp;#39;)
]

...
self.executor_config = {
    &amp;quot;pod_override&amp;quot;: k8s.V1Pod(
        metadata=k8s.V1ObjectMeta(
            namespace=&amp;#39;some_namespace&amp;#39;
        ),
        spec=k8s.V1PodSpec(
            containers=[
                k8s.V1Container(
                    name=&amp;#39;base&amp;#39;,
                    volume_mounts=K8S_VOLUME_MOUNTS,
                    security_context=k8s.V1SecurityContext(
                        run_as_group=0
                    )
                )
            ],
            volumes=K8S_VOLUMES,
        )
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;executor_config&lt;/code&gt; 는 &lt;code&gt;pod_override&lt;/code&gt; 필드와 &lt;code&gt;KubernetesExecutor&lt;/code&gt; 필드를 인식합니다. 다만, &lt;code&gt;KubernetesExecutor&lt;/code&gt; 는 예전에 사용되던 필드의 이름이고, 현재는 &lt;code&gt;pod_override&lt;/code&gt; 필드를 사용하면 됩니다. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;KubernetesExecutor&lt;/code&gt; 필드를 사용한다면 &lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/1.10.9/_api/airflow/contrib/executors/kubernetes_executor/index.html#airflow.contrib.executors.kubernetes_executor.KubernetesExecutorConfig&quot;&gt;본 문서&lt;/a&gt;를 참조해보세요.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pod_override&lt;/code&gt; 필드는 &lt;a href=&quot;https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Pod.md&quot;&gt;이 문서&lt;/a&gt;를 참조해서 Pod 명세를 오브젝트로 표현해주면 됩니다.&lt;/p&gt;
&lt;p&gt;만약 둘 다 사용하려고 하면, 하나만 쓰라는 에러가 발생합니다. 그러니 둘 중에 하나를 사용해주세요! 되도록이면 &lt;code&gt;pod_override&lt;/code&gt; 필드를 사용하는 것을 추천드립니다. &lt;/p&gt;
&lt;h1&gt;그러나… 네임스페이스 버그가 있어요&lt;/h1&gt;
&lt;p&gt;그러나 현재 최신 버전까지도 이 override 옵션에 대해 버그가 존재하는데요.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;metadata=k8s.V1ObjectMeta(
    namespace=&amp;#39;some_namespace&amp;#39;
),&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드처럼 직접 정의한 네임스페이스가 적용되지 않는 버그입니다. (&lt;a href=&quot;https://github.com/apache/airflow/pull/24342&quot;&gt;https://github.com/apache/airflow/pull/24342&lt;/a&gt;)  이 버그는 최근에 머지된 코드를 통해 해결할 수 있는데요. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;airflow/kubernetes/pod_generator.py&lt;/code&gt; 코드를 수정해주면 됩니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;try:
    final_namespace = pod_override_object.metadata.namespace  # type: ignore
    if not final_namespace:
        final_namespace = namespace
except Exception:
    final_namespace = namespace&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 변경 내역을 보면, 이 코드처럼 네임스페이스 오버라이드를 하는 경우엔 최종 네임스페이스를 변경하려는 네임스페이스로 바꿔주고, 그렇지 않다면, 기본값 네임스페이스가 최종 네임스페이스가 되도록 반영해주는 매우.. 매우 단순한 코드가 추가 됩니다. &lt;/p&gt;
&lt;p&gt;분명.. 잘 정의했는데.. 이게 왜 적용이 안 되나, 뭘 잘못 정의해서 그런건가.. 싶어서 헤매던 시간을 생각해보면 아직도 아찔합니다 ㅎㅎ &lt;/p&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;p&gt;매우 짧은 글이지만, 단시간 안에 PythonSensor에 pod 수정 옵션을 주어야 했던 저에게는 귀중한 정보라서 이렇게 공유드리게 되었습니다. 순전히 제가 급하게 알아냈던 정보들이니 틀린 정보가 있을 수 있습니다.  &lt;/p&gt;
&lt;p&gt;만약 이 글이 도움이 되셨다면 글 좌측 하단의 하트❤를 눌러주시면 감사하겠습니다.&lt;/p&gt;
&lt;p&gt;혹시라도 글에 이상이 있거나, 오역, 이상한 번역이 있거나, 이해가 가지 않으시는 부분, 또는 추가적으로 궁금하신 내용이 있다면 주저 마시고 댓글 을 남겨주세요! 빠른 시간 안에 답변을 드리겠습니다  &lt;/p&gt;</description>
      <category>IT/Airflow</category>
      <category>Airflow</category>
      <category>k8s</category>
      <category>KubernetesExecutor</category>
      <category>PythonOperator</category>
      <category>PythonSensor</category>
      <category>에어플로</category>
      <author>wookiist</author>
      <guid isPermaLink="true">https://wookiist.tistory.com/174</guid>
      <comments>https://wookiist.tistory.com/174#entry174comment</comments>
      <pubDate>Sun, 4 Sep 2022 20:32:13 +0900</pubDate>
    </item>
    <item>
      <title>[Hadoop] WebHDFS와 HttpFS는 무엇이 다른걸까?</title>
      <link>https://wookiist.tistory.com/173</link>
      <description>&lt;h1&gt;Intro&lt;/h1&gt;
&lt;p&gt;얼마전, 회사에서 MLE 분이 HDFS에서 뭔가 다운로드 받고 싶으시다고 WebHDFS 경로를 문의주셨습니다. 그런데 저희 시니어 팀원 분께서 WebHDFS가 아닌 HttpFS를 쓰시도록 권고를 해주시길래, 어떤 차이가 있는걸까.. 생각해보게 되었습니다. 단순한 궁금증이긴 하지만, 이번 기회에 정리해보고자 합니다.&lt;/p&gt;
&lt;p&gt;WebHDFS와 HttpFS. 과연 무엇이 다른걸까요?&lt;/p&gt;
&lt;h1&gt;WebHDFS&lt;/h1&gt;
&lt;p&gt;WebHDFS는 HDFS 클라이언트가 Hadoop 바이너리를 설치하지 않더라도, 다양한 언어에서 HDFS에 접근할 수 있도록 REST 형태로 개발된 API입니다. Hortonworks에서 개발했으며, Hadoop 에코시스템 외부에서 동작하는 애플리케이션이 HDFS에 무언가 생성하고 쓰고, 변경하는 작업을 허용해줍니다. &lt;/p&gt;
&lt;p&gt;WebHDFS는 REST를 기반으로 하기 때문에 GET, PUT, POST, DELETE 등의 HTTP 메서드를 활용합니다. 예를 들어, &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;getfilestatus&lt;/code&gt;, &lt;code&gt;liststatus&lt;/code&gt; 같은 작업은 &lt;code&gt;GET&lt;/code&gt; 메서드를 활용하고, &lt;code&gt;create&lt;/code&gt; , &lt;code&gt;mkdirs&lt;/code&gt; , &lt;code&gt;rename&lt;/code&gt; 등의 작업은 &lt;code&gt;PUT&lt;/code&gt; 메서드를 활용합니다. &lt;/p&gt;
&lt;p&gt;WebHDFS는 Hadoop 설치 과정에서 &lt;code&gt;hdfs-site.xml&lt;/code&gt; 파일 설정에 따라 기본적으로 함께 설치됩니다. 다음처럼 &lt;code&gt;dfs.webhdfs.enabled&lt;/code&gt; 옵션이 &lt;code&gt;true&lt;/code&gt; 로 설정되어 있다면 함께 설치됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;property&amp;gt;
  &amp;lt;name&amp;gt;dfs.webhdfs.enabled&amp;lt;/name&amp;gt;
  &amp;lt;value&amp;gt;true&amp;lt;/value&amp;gt;
&amp;lt;/property&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;연결하는 방법도 단순합니다. 클라이언트가 액티브 네임노드에 직접 API를 호출하는 형태이기 때문에 다음과 같은 URI를 갖게 됩니다. &lt;/p&gt;
&lt;p&gt;참고로 기본 &lt;code&gt;&amp;lt;PORT&amp;gt;&lt;/code&gt; 의 값은 네임노드의 경우 &lt;code&gt;50070&lt;/code&gt;, 데이터노드의 경우 &lt;code&gt;50075&lt;/code&gt; 가 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# &amp;lt;PATH&amp;gt; 디렉토리 내 파일 보기
curl -i &amp;quot;http://&amp;lt;HOST&amp;gt;:&amp;lt;PORT&amp;gt;/webhdfs/v1/&amp;lt;PATH&amp;gt;?op=LISTSTATUS&amp;quot;

# &amp;lt;PATH&amp;gt; 컨텐츠 요약하기(?)
curl -i &amp;quot;http://&amp;lt;HOST&amp;gt;:&amp;lt;PORT&amp;gt;/webhdfs/v1/&amp;lt;PATH&amp;gt;?op=GETCONTENTSUMMARY&amp;quot;

# &amp;lt;PATH&amp;gt;에 위치한 파일 읽기
curl -i -L &amp;quot;http://&amp;lt;HOST&amp;gt;:&amp;lt;PORT&amp;gt;/webhdfs/v1/&amp;lt;PATH&amp;gt;?op=OPEN&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;HttpFS (HDFS over HTTP)&lt;/h1&gt;
&lt;p&gt;HttpFS는 Hadoop 바이너리가 없는 외부에서, HTTP 프로토콜을 통해 HDFS에 직접 접근하기 위한 게이트웨이의 역할을 합니다. 즉, WebHDFS처럼 직접 액티브 네임노드에 붙는 형태가 아니라, HttpFS 서버를 띄우고, 클라이언트는 이 서버와 통신하는 구조를 갖습니다. 프록시 같은 역할이라고 볼 수 있겠네요!&lt;/p&gt;
&lt;p&gt;따라서 HA 활성화된 클러스터인 경우에도 접근이 가능합니다. 액티브 네임노드가 변경되더라도, 이를 클라이언트가 알고 있을 필요가 없으니까요. &lt;/p&gt;
&lt;p&gt;HttpFS를 이용할 때, 모든 데이터는 단일 HttpFS 노드를 통해서 오가게 됩니다. 따라서 성능 면에서는 느릴 수도 있지만, 다른 Hadoop 노드의 방화벽을 오픈할 필요가 없기 때문에 보안을 향상 시키는데 도움이 됩니다. &lt;/p&gt;
&lt;p&gt;HttpFS API를 몇 가지 살펴보면 다음과 같습니다. 참고로 &lt;code&gt;&amp;lt;PORT&amp;gt;&lt;/code&gt; 의 기본값은 &lt;code&gt;14000&lt;/code&gt;으로 설정되어 있습니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# /user/foo/README.txt 파일 읽기
curl &amp;#39;http://&amp;lt;HTTPFS_HOST&amp;gt;:&amp;lt;PORT&amp;gt;/webhdfs/v1/user/foo/README.txt?op=OPEN&amp;amp;user.name=foo&amp;#39; 

# /user/foo 디렉토리 내 파일 리스트를 JSON 형태로 받기
curl &amp;#39;http://&amp;lt;HTTPFS_HOST&amp;gt;:&amp;lt;PORT&amp;gt;/webhdfs/v1/user/foo?op=LISTSTATUS&amp;amp;user.name=foo&amp;#39; returns the contents of the HDFS /user/foo directory in JSON format.

# /usre/foo/bar 디렉토리를 생성하기
curl -X POST &amp;#39;http://&amp;lt;HTTPFS_HOST&amp;gt;:&amp;lt;PORT&amp;gt;/webhdfs/v1/user/foo/bar?op=MKDIRS&amp;amp;user.name=foo&amp;#39; creates the HDFS /user/foo/bar directory.&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;차이점은?&lt;/h1&gt;
&lt;p&gt;이미 앞선 설명을 읽으시면서 느끼셨겠지만, WebHDFS와 HttpFS는 그리 다르지 않습니다. 둘 다 하는 역할이 비슷하죠. 그렇지만 구조적인 차이로 인해 큼직한 차이점이 있습니다. &lt;/p&gt;
&lt;p&gt;WebHDFS의 경우, &lt;code&gt;클라이언트 &amp;lt;-&amp;gt; [네임노드, 데이터노드]&lt;/code&gt; 사이의 방화벽이 모두 열려야 합니다. 그렇다보니 매번 방화벽을 신경 써줘야 한다는 불편함이 있습니다. 또, 저렇게 모든 방화벽을 열게 되면, 당연히 보안도 취약해질 수밖에 없습니다. &lt;/p&gt;
&lt;p&gt;반면, HttpFS의 경우, &lt;code&gt;클라이언트 &amp;lt;-&amp;gt; HttpFS 서버&lt;/code&gt; 의 구조가 되기 때문에, HttpFS를 제공하는 서버로의 방화벽만 열리면 되기 때문에, 보안적인 측면에서 훨씬 견고합니다.&lt;/p&gt;
&lt;p&gt;게다가, 앞서 말씀드렸듯이  HttpFS는 프록시와 비슷한 역할을 하는 서버를 제공하기 때문에 HA 구성 되어 있는 Hadoop 클러스터도 잘 지원합니다. WebHDFS는 액티브 네임노드의 주소가 변경되면 클라이언트가 이를 인지해야 하기 때문에 HA 구성된 클러스터에서 사용하기 어려움이 있습니다. &lt;/p&gt;
&lt;p&gt;그렇지만, WebHDFS는 클라이언트가 데이터를 읽을 때, 각 데이터를 보유하고 있는 데이터노드에서 직접 데이터를 불러오기 때문에, 단일 서버와 통신하는 HttpFS 방식에 비해 작업 성능은 뛰어나다고 볼 수 있습니다. 이런 이유로, 대용량 파일이 전송되는 경우 HttpFS 서버에 부하가 크게 가해질 수 있다는 점은 인지할 필요가 있습니다.&lt;/p&gt;
&lt;p&gt;정리하자면, 보안이 중요한 클러스터라면 HttpFS를, 보안은 그리 중요하지 않지만 성능을 챙겨야 하는 클러스터를 사용중이라면 WebHDFS를 선택하면 되겠네요! &lt;/p&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;p&gt;이번 글을 작성하면서 HttpFS와 WebHDFS의 역할과 차이점을 확실하게 이해할 수 있었습니다. 특히, Reference 쪽에 소개한 slideshare 자료를 보면, 두 방식이 어떠한 플로우로 동작하는지 쉽게 이해할 수 있는 차트가 있어서 궁금하시다면 한번 살펴보시는 걸 추천드립니다!&lt;/p&gt;
&lt;p&gt;만약 이 글이 도움이 되셨다면 글 좌측 하단의 하트❤를 눌러주시면 감사하겠습니다.&lt;/p&gt;
&lt;p&gt;혹시라도 글에 이상이 있거나, 오역, 이상한 번역이 있거나, 이해가 가지 않으시는 부분, 또는 추가적으로 궁금하신 내용이 있다면 주저 마시고 댓글 을 남겨주세요! 빠른 시간 안에 답변을 드리겠습니다  &lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/31580832/hdfs-put-vs-webhdfs&quot;&gt;https://stackoverflow.com/questions/31580832/hdfs-put-vs-webhdfs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://community.cloudera.com/t5/Community-Articles/Comparison-of-HttpFs-and-WebHDFS/ta-p/245562&quot;&gt;https://community.cloudera.com/t5/Community-Articles/Comparison-of-HttpFs-and-WebHDFS/ta-p/245562&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hadoop.apache.org/docs/stable/hadoop-hdfs-httpfs/index.html&quot;&gt;https://hadoop.apache.org/docs/stable/hadoop-hdfs-httpfs/index.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hadoop.apache.org/docs/r1.0.4/webhdfs.html&quot;&gt;https://hadoop.apache.org/docs/r1.0.4/webhdfs.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/pulse/20140717115238-176301000-accessing-hdfs-using-the-webhdfs-rest-api-vs-httpfs/&quot;&gt;https://www.linkedin.com/pulse/20140717115238-176301000-accessing-hdfs-using-the-webhdfs-rest-api-vs-httpfs/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.slideshare.net/wchevreuil/web-hdfs-and-httpfs&quot;&gt;https://www.slideshare.net/wchevreuil/web-hdfs-and-httpfs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Hadoop</category>
      <category>data</category>
      <category>Data Engineering</category>
      <category>De</category>
      <category>hadoop</category>
      <category>HDFS</category>
      <category>HttpFS</category>
      <category>WebHDFS</category>
      <category>데이터 엔지니어</category>
      <category>하둡</category>
      <author>wookiist</author>
      <guid isPermaLink="true">https://wookiist.tistory.com/173</guid>
      <comments>https://wookiist.tistory.com/173#entry173comment</comments>
      <pubDate>Sun, 7 Aug 2022 17:31:19 +0900</pubDate>
    </item>
    <item>
      <title>[Storage] Block Storage와 File Storage</title>
      <link>https://wookiist.tistory.com/172</link>
      <description>&lt;h1&gt;Block Storage vs. File Storage&lt;/h1&gt;
&lt;p&gt;최근 Ceph 관련 장애를 겪으면서, 스토리지 개념에 대해 정리 해볼 필요성을 느꼈습니다. 대충, ReadWriteOnce로 쓸 때는 Ceph-Block 을 쓰면 되고, ReadWriteMany로 생성하고 싶을 땐, Ceph-Filesystem을 사용하면 된다는 것은 알겠지만, 왜 그런 것일까요? 오늘 글에서는 둘은 어떻게 다르고, 어떤 특징을 가지고 있는지 알아보겠습니다.&lt;/p&gt;
&lt;h1&gt;File Storage&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/wookiist/tistory-images/blob/main/2022/07/24/file_storage_v2.png?raw=true&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;p&gt;File Storage의 역사는 꽤 오래되었습니다. 그리고 유저들에게 가장 친숙한 스토리지 시스템이기도 합니다. 우리의 파일 또는 데이터에 이름을 붙여주고, 이걸 ‘폴더&amp;#39;에 저장합니다. 그리고 위 이미지에서 볼 수 있는 것처럼 파일에 접근할 때, 계층적으로 접근하게 됩니다. 따라서, 우리가 데이터에 액세스해야 하는 경우, 정확한 경로를 알아야 찾을 수 있습니다. (물론 ‘검색’을 활용하면 메타데이터를 이용해서 대강 찾아낼 수는 있지만, 시간이 꽤 오래 걸립니다)&lt;/p&gt;
&lt;p&gt;File Storage는 파일 자체적으로 메타데이터를(이름, 위치, 생성일, 크기 등) 관리하고 있고, 이를 계층적 구조로 유지하고 있기 때문에, 개별 파일 단위로 Lock을 걸 수 있습니다. 따라서 여러 사람이 동시에 파일에 접근하는 것을 허용하며, ReadWriteMany로 쓸 수 있는 근거가 됩니다. &lt;/p&gt;
&lt;p&gt;File Storage는 거의 모든 데이터를 저장할 수 있으며, 다수의 복잡한 구조의 형태를 저장하기에 유리하며, 사용자가 쉽게 탐색할 수 있습니다. &lt;/p&gt;
&lt;p&gt;File Storage의 용량을 키울 땐, 용량을 늘려주는 것만으로는 불가능합니다. 추가로 더 많은 파일 시스템을 추가해 Scale Out 해주어야 합니다.&lt;/p&gt;
&lt;p&gt;File Storage를 제공하는 서비스로는 Amazon의 EFS (Elastic File System), Google의 Google Cloud Filestore 등이 있습니다. &lt;/p&gt;
&lt;h1&gt;Block Storage&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/wookiist/tistory-images/blob/main/2022/07/24/block_storage.png?raw=true&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;p&gt;Block Storage는 가장 오래되고 간단한 형태의 데이터 스토리지입니다. 위 이미지에서 볼 수 있듯이, Block Storage 시스템에서 데이터는 “block”이라 불리는 고정된 사이즈의 조각으로 쪼개져 저장됩니다. 이렇게 쪼개진 조각들은 다양한 시스템에 분산되어 저장되어 있다가, 사용자의 파일 요청이 있을 때, 해당 파일을 구성하는 조각들을 모음하여 원본 파일로 만들어 돌려줍니다. Block Storage를 사용하는 경우, 애플리케이션은 블록들의 올바른 주솟값을 찾기 위해 SCSI 요청을 보냅니다. 그리고 그렇게 모아온 블록을 하나로 합쳐 완전한 파일로 만들어줍니다. 다만, 데이터가 쪼개져있기 때문에, 블록들이 비슷한 위치에 저장되어 있다면 빠른 성능을 보여주지만, 여기저기 멀리 떨어져 존재한다면, 그만큼 성능도 저하된다는 문제가 있습니다. &lt;/p&gt;
&lt;p&gt;그럼에도 File Storage처럼 단일 데이터 path에 의존하고 있지 않기 때문에 꽤 빠른 검색이 가능합니다. 또한 파일의 개수가 늘어나면 성능이 저하되는 File Storage와는 달리, Block Storage는 데이터가 늘어나도 성능 저하가 거의 없기 때문에, 더 많은 데이터를 이용해야 한다면 Block Storage를 적용하는 것이 좋습니다. &lt;/p&gt;
&lt;p&gt;Block Storage의 용량을 키우는 방법은 간단합니다. 노드를 좀 더 붙여주면 됩니다. &lt;/p&gt;
&lt;p&gt;다만, File Storage와는 달리 Block Storage에선 파일의 무결성을 보장하기 위해 전체 볼륨을 Lock 하고, Read-Only로 만듭니다. 이로 인해 여러 사람이 동시에 파일에 접근해서 쓰거나 읽는 작업을 수행하는 것이 불가능한 구조입니다. (ReadWriteOnce로만 이용 가능)&lt;/p&gt;
&lt;p&gt;Block Storage를 제공하는 클라우드 서비스로는 Amazon의 EBS (Elastic Block Store), Google의 Google Cloud Storage가 있습니다. &lt;/p&gt;
&lt;h1&gt;무엇을 선택해야할까?&lt;/h1&gt;
&lt;p&gt;그렇다면 Block Storage와 File Storage 중 어떤 것을 선택해야할까요? 정해진 답은 없습니다. 사용하려는 Application의 특성에 맞게 선택하면 됩니다. &lt;/p&gt;
&lt;p&gt;예를 들어, 여러 대의 VM 인스턴스가 부팅 시에 참조하는 부트 이미지를 저장하는 부트 볼륨으로 사용해야 하는 경우, Block Storage를 사용하면 됩니다. &lt;/p&gt;
&lt;p&gt;만약, 굉장히 낮은 지연시간을 필요로 하는 트랜젹션이 가능한 DB 또는 관계형 DB를 사용하는 경우에도 Block Storage를 사용하면 됩니다. &lt;/p&gt;
&lt;p&gt;때로는 정형 데이터 뿐만 아니라, 비정형 데이터까지 섞인 데이터를 취급해야 하는 경우에는 File Storage를 선택하는 것이 좋습니다. 예를 들면, 텍스트 파일과 미디어 데이터를 동시에 취급하는 웹 호스팅 서버를 제공하는 경우가 이 케이스에 해당합니다. &lt;/p&gt;
&lt;p&gt;마지막으로, Read와 Write 작업이 동시에 일어나는 등의 협업 공간이 필요한 경우 File Storage를 사용하면 됩니다. &lt;/p&gt;
&lt;p&gt;지금까지 다룬 내용들을 정리해보면, 아래 이미지처럼 정리할 수 있습니다. GCP에서 정리한 글에서 발췌한 “어떤 스토리지를 사용해야 할까요?”라는 제목의 이미지입니다. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/wookiist/tistory-images/blob/main/2022/07/24/gcp_which_storage.png?raw=true&quot; alt=&quot;출처 : [https://cloud.google.com/blog/topics/developers-practitioners/map-storage-options-google-cloud](https://cloud.google.com/blog/topics/developers-practitioners/map-storage-options-google-cloud)&quot;&gt;&lt;/p&gt;
&lt;p&gt;출처 : &lt;a href=&quot;https://cloud.google.com/blog/topics/developers-practitioners/map-storage-options-google-cloud&quot;&gt;https://cloud.google.com/blog/topics/developers-practitioners/map-storage-options-google-cloud&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;끝으로&lt;/h1&gt;
&lt;p&gt;충분히 조사하지 못하고 글을 작성하려니, 어려움이 많았습니다. 이후 Object Storage까지 곁들여 조사한 내용을 바탕으로 글을 써볼 계획인데, 이때 조금 더 보완해보아야겠습니다.&lt;/p&gt;
&lt;p&gt;만약 이 글이 도움이 되셨다면 글 좌측 하단의 하트❤를 눌러주시면 감사하겠습니다.&lt;/p&gt;
&lt;p&gt;혹시라도 글에 이상이 있거나, 오역, 이상한 번역이 있거나, 이해가 가지 않으시는 부분, 또는 추가적으로 궁금하신 내용이 있다면 주저 마시고 댓글 을 남겨주세요! 빠른 시간 안에 답변을 드리겠습니다  &lt;/p&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.redhat.com/ko/topics/data-storage/file-block-object-storage&quot;&gt;https://www.redhat.com/ko/topics/data-storage/file-block-object-storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.datacore.com/blog/three-types-of-storage-block-file-object/&quot;&gt;https://www.datacore.com/blog/three-types-of-storage-block-file-object/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.alibabacloud.com/ko/knowledge/difference-between-object-storage-file-storage-block-storage&quot;&gt;https://www.alibabacloud.com/ko/knowledge/difference-between-object-storage-file-storage-block-storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@leesjpr/%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80File-storage-Block-storage-Object-storage&quot;&gt;https://velog.io/@leesjpr/스토리지File-storage-Block-storage-Object-storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/hwclong911/article/details/90240767&quot;&gt;https://blog.csdn.net/hwclong911/article/details/90240767&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/blog/topics/developers-practitioners/map-storage-options-google-cloud&quot;&gt;https://cloud.google.com/blog/topics/developers-practitioners/map-storage-options-google-cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rajkumaraug20.medium.com/file-storage-vs-block-storage-vs-object-storage-2519031a2646&quot;&gt;https://rajkumaraug20.medium.com/file-storage-vs-block-storage-vs-object-storage-2519031a2646&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/IT WIKI</category>
      <category>block storage</category>
      <category>file storage</category>
      <category>Storage</category>
      <category>스토리지</category>
      <category>저장소</category>
      <author>wookiist</author>
      <guid isPermaLink="true">https://wookiist.tistory.com/172</guid>
      <comments>https://wookiist.tistory.com/172#entry172comment</comments>
      <pubDate>Sun, 24 Jul 2022 11:08:10 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] Pool</title>
      <link>https://wookiist.tistory.com/171</link>
      <description>&lt;h1&gt;Pool&lt;/h1&gt;
&lt;p&gt;Airflow가 동시에 실행하는 Task가 너무 많을 경우, 시스템에 부하를 줄 수 있습니다. Pool은 이러한 문제가 발생하는 것을 방지하기 위해, 해당 Pool을 사용하는 DAG들에서 병렬로 실행되는 Task의 개수를 제한하기 위해 도입되었습니다.&lt;/p&gt;
&lt;p&gt;Pool을 사용하면, Task Set에 대한 병렬 처리를 제한하여 각 Task가 실행되는 시기를 세밀하게 제어할 수 있습니다. 이 내용에 대해서는 마지막에 usecase를 보면서 설명드리겠습니다. 특히, 특정 Task를 수행하는 병렬 Task의 수를 제한하기 위해 자주 사용합니다. 예를 들어, 동일한 API Endpoint나 DB를 찌르는 작업일 때, 또는 Kubernetes 클러스터의 GPU 노드에서 GPU 할당을 제어하기 위해 사용합니다.&lt;/p&gt;
&lt;p&gt;기본적으로 Airflow의 모든 Task는 별도의 명시된 Pool이 없는한, &lt;code&gt;default_pool&lt;/code&gt; 이라는 이름의 Pool에 할당됩니다. 이 Pool에는 128개의 Slot이 기본값으로 설정되어 있습니다. 이 값은 수정이 가능합니다만, &lt;code&gt;default_pool&lt;/code&gt; 을 삭제할 수는 없습니다. &lt;/p&gt;
&lt;p&gt;만약, &lt;code&gt;default_pool&lt;/code&gt; 을 사용하지 않고 별도 Pool을 이용하려면 다음처럼 &lt;code&gt;pool&lt;/code&gt; 파라미터를 작성해주면 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;task_a = PythonOperator(
    task_id=&amp;#39;sample&amp;#39;,
    python_callable=some_function,
    **pool=&amp;#39;single_task_pool&amp;#39;**
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;별도의 Pool을 생성하는 방법은 매우 간단합니다. CLI를 활용하는 방법도 있고, Web UI에서 생성하는 방법도 있습니다. 여기에선 Web UI에서 생성해보도록 하겠습니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;상단의 &lt;code&gt;Admin&lt;/code&gt; 메뉴에서 &lt;code&gt;Pool&lt;/code&gt; 메뉴를 선택하고 새로운 Pool을 추가합니다. 다음처럼 Pool의 이름과, Slot의 개수 그리고 해당 Pool에 대한 설명을 정의할 수 있습니다.&lt;/p&gt;
&lt;p&gt;  &lt;img src=&quot;https://github.com/wookiist/tistory-images/blob/main/2022/07/09/airflow_pool.png?raw=true&quot; alt=&quot;https://github.com/wookiist/tistory-images/blob/main/2022/07/09/airflow_pool.png?raw=true&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Priority Weight in Pool&lt;/h1&gt;
&lt;p&gt;같은 Pool 내에 속해있더라도, 모든 Task가 같은 중요도를 갖지 않을 수 있습니다. 중요도가 더 높은 Task에 가중치를 더 주어서, 해당 Task에 비해 상대적으로 중요도가 낮은 Task보다 해당 Task를 먼저 실행할 수 있습니다. &lt;/p&gt;
&lt;p&gt;이 값은 &lt;code&gt;priority_weight&lt;/code&gt; 라는 파라미터를 설정해서 pool level에서 제어할 수 있습니다. &lt;code&gt;priority_weight&lt;/code&gt; 는 임의의 정수 값을 가질 수 있으며, 기본값은 &lt;code&gt;1&lt;/code&gt; 입니다. 이 값이 &lt;strong&gt;더 높을수록&lt;/strong&gt; 우선 순위가 더 높아집니다. &lt;/p&gt;
&lt;p&gt;다음과 같은 DAG를 예로 들어보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with DAG(
    dag_id=&amp;#39;pool_priority_dag&amp;#39;,
    schedule_interval=@daily,
    catchup=False,
    default_args=default_args
) as dag:

  task_a = PythonOPerator(
        task_id=&amp;#39;task_a&amp;#39;,
        python_callable=some_function,
        pool=&amp;#39;single_task_pool&amp;#39;
    )

    task_b = PythonOperator(
        task_id=&amp;#39;task_b&amp;#39;,
        python_callable=some_function,
        pool=&amp;#39;single_task_pool&amp;#39;,
        priority_weight=2
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위처럼 작성했을 때, &lt;code&gt;task_a&lt;/code&gt; 와 &lt;code&gt;task_b&lt;/code&gt; 는 모두 &lt;code&gt;single_task_pool&lt;/code&gt; 이라는 이름의 pool에 할당됩니다. &lt;code&gt;task_a&lt;/code&gt; 의 경우, &lt;code&gt;priority_weight&lt;/code&gt;를 별도로 작성하지 않았으므로 &lt;code&gt;1&lt;/code&gt; 이라는 값을 갖게 됩니다. &lt;code&gt;task_b&lt;/code&gt; 는 &lt;code&gt;2&lt;/code&gt; 라고 명시하고 있으므로, &lt;code&gt;task_a&lt;/code&gt; 에 비해 상대적으로 가중치가 더 높은 &lt;code&gt;task_b&lt;/code&gt; 가 먼저 실행됩니다. &lt;/p&gt;
&lt;h1&gt;하나의 Task에 여러 Slot 할당하기&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;pool_slots&lt;/code&gt; 라는 파라미터를 추가해주면, 하나의 Task가 해당 Pool에서 &lt;code&gt;pool_slots&lt;/code&gt; 만큼의 Slot 동시에 점유하도록 만들 수 있습니다. 특히, 무거운 작업을 수행해야 한다면, 가벼운 작업에 비해 상대적으로 더 많은 리소스를 활용할 수 있도록 충분한 여지를 주고자 할 때 활용하면 좋습니다. 다음과 같은 예시를 들어보겠습니다. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;maintenance&lt;/code&gt; 라는 이름의 Pool이 있고, 해당 Pool의 Slot은 &lt;code&gt;2&lt;/code&gt; 개라고 가정하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;BashOperator(
    task_id=&amp;quot;heavy_task&amp;quot;,
    bash_command=&amp;quot;bash backup_data.sh&amp;quot;,
    pool_slots=2,
    pool=&amp;quot;maintenance&amp;quot;,
)

BashOperator(
    task_id=&amp;quot;light_task1&amp;quot;,
    bash_command=&amp;quot;bash check_files.sh&amp;quot;,
    pool_slots=1,
    pool=&amp;quot;maintenance&amp;quot;,
)

BashOperator(
    task_id=&amp;quot;light_task2&amp;quot;,
    bash_command=&amp;quot;bash remove_files.sh&amp;quot;,
    pool_slots=1,
    pool=&amp;quot;maintenance&amp;quot;,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;heavy_task&lt;/code&gt; 가 실행되면 &lt;code&gt;maintenance&lt;/code&gt; Pool의 Slot을 2개 점유하므로 해당 Pool의 Slot이 고갈됩니다. 따라서 나머지 &lt;code&gt;light_task[1,2]&lt;/code&gt; 는 &lt;code&gt;heavy_task&lt;/code&gt;와 동시에 실행될 수 없고, &lt;code&gt;heavy_task&lt;/code&gt;가 끝나기를 기다려야 합니다. 이렇게 하면, &lt;code&gt;heavy_task&lt;/code&gt; 와 &lt;code&gt;light_task&lt;/code&gt; 가 동시에 실행되었을 때 발생할 수 있는 시스템 과부하를 방지할 수 있습니다. &lt;/p&gt;
&lt;h1&gt;Limitation&lt;/h1&gt;
&lt;p&gt;그러나 Pool의 능력에도 한계는 있습니다. Pool을 사용하기 전에 다음과 같은 사항을 염두에 둘 필요가 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;각 Task는 단일 Pool에만 할당할 수 있습니다. 즉, 여러 Pool에 동시에 할당할 수 없다는 점을 알아둬야 합니다. 선택적으로 가져가는 것도 불가능합니다.&lt;/li&gt;
&lt;li&gt;SubDAG를 작성한 경우, SubDAG 내에 있는 Task에 Pool을 직접 적용해줘야 합니다. 즉, 상위 DAG에서 일괄로 적용한 pool은 SubDAG의 Task에는 적용되지 않는다는 점을 인지하고 있어야 합니다.&lt;/li&gt;
&lt;li&gt;Pool은 Task Instance의 병렬 처리를 제어하고자 하는 목적으로 도입되었습니다. Task Instance가 아닌 단일 DAG나 모든 DAG에 대한 DagRun 동시성을 제어하고자 하는 경우라면, &lt;code&gt;max_active_runs&lt;/code&gt; 또는 &lt;code&gt;core.max_active_runs_per_dag&lt;/code&gt; 파라미터를 확인해보는 것이 좋습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;UseCase: API Endpoint를 찌르는 작업의 수를 제한하기&lt;/h1&gt;
&lt;p&gt;예를 들어, 한 번에 3개의 요청만 받기를 원하는 API Endpoint가 있다고 가정해보겠습니다. 본 시나리오에서는 DAG 일정에 따라 동시에 API Endpoint를 찌르게 될 수 있는 2개의 서로 다른 DAG에 총 5개의 Task가 있습니다. 다만, 한 번에 최대 3개의 Task만 해당 API Endpoint를 찌르게 하고 싶으므로, 3개의 Slot을 가진 &lt;code&gt;api_pool&lt;/code&gt; 이라는 Pool을 하나 생성하겠습니다.&lt;/p&gt;
&lt;p&gt;아래 &lt;code&gt;pool_priority_dag&lt;/code&gt; DAG를 살펴보면, &lt;code&gt;default_args&lt;/code&gt; 파라미터에 &lt;code&gt;pool&lt;/code&gt; 과 &lt;code&gt;priority_weight&lt;/code&gt; 값을 설정하고 있습니다. 이렇게 하면 해당 DAG에 속한 모든 Task (단, SubDAG 제외) 에 일괄 적용됩니다. 따라서, &lt;code&gt;task_a&lt;/code&gt; , &lt;code&gt;task_b&lt;/code&gt; , &lt;code&gt;task_c&lt;/code&gt; 는 동시에 &lt;code&gt;api_pool&lt;/code&gt; 을 점유하여 실행되며, API Endpoint에 요청을 보낼 수 있습니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pool_priority_dag&lt;/code&gt; 코드&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  import requests
  from datetime import datetime, timedelta

  from airflow import DAG
  from airflow.operators.python_operator import PythonOperator

  def api_function(**kwargs):
      url = &amp;#39;https://covidtracking.com/api/v1/states/&amp;#39;
      filename = &amp;#39;{0}/{1}.csv&amp;#39;.format(&amp;#39;wa&amp;#39;, &amp;#39;2021-07-08&amp;#39;)
      res = requests.get(url+filename)
      return res

  with DAG(
          dag_id=&amp;#39;pool_priority_dag&amp;#39;,
          start_date=datetime(2022, 7, 9),
          schedule_interval=&amp;#39;@daily&amp;#39;,
          catchup=False,
          default_args={
              **&amp;#39;pool&amp;#39;: &amp;#39;api_pool&amp;#39;,**
              &amp;#39;retries&amp;#39;: 1,
              &amp;#39;retry_delay&amp;#39;: timedelta(minutes=5),
              **&amp;#39;priority_weight&amp;#39;: 3**
          }
      ) as dag:

      task_a = PythonOperator(
          task_id=&amp;#39;task_a&amp;#39;,
          python_callable=api_function
      )

      task_b = PythonOperator(
          task_id=&amp;#39;task_b&amp;#39;,
          python_callable=api_function
      )

      task_c = PythonOperator(
          task_id=&amp;#39;task_c&amp;#39;,
          python_callable=api_function
      )

      [task_a, task_b, task_c]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;해당 시점에는 &lt;code&gt;pool_priority_dag&lt;/code&gt; DAG이 실행됨과 동시에, 아래의 &lt;code&gt;pool_unimportant_dag&lt;/code&gt; DAG도 함께 실행되고 있습니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pool_unimportant_dag&lt;/code&gt; 코드&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  import requests
  from datetime import datetime, timedelta

  from airflow import DAG
  from airflow.operators.dummy_operator import DummyOperator
  from airflow.operators.python_operator import PythonOperator

  def api_function(**kwargs):
      url = &amp;#39;https://covidtracking.com/api/v1/states/&amp;#39;
      filename = &amp;#39;{0}/{1}.csv&amp;#39;.format(&amp;#39;wa&amp;#39;, &amp;#39;2021-07-08&amp;#39;)
      res = requests.get(url+filename)
      return res

  with DAG(
          dag_id=&amp;#39;pool_unimportant_dag&amp;#39;,
          start_date=datetime(2022, 7, 9),
          schedule_interval=&amp;#39;@daily&amp;#39;,
          catchup=False,
          default_args={
              &amp;#39;retries&amp;#39;: 1,
              &amp;#39;retry_delay&amp;#39;: timedelta(minutes=5)
          }
      ) as dag:

      task_w = DummyOperator(
          task_id=&amp;#39;start&amp;#39;
      )

      task_x = PythonOperator(
          task_id=&amp;#39;task_x&amp;#39;,
          python_callable=api_function,
          **pool=&amp;#39;api_pool&amp;#39;,**
          **priority_weight=2**
      )

      task_y = PythonOperator(
          task_id=&amp;#39;task_y&amp;#39;,
          python_callable=api_function,
          **pool=&amp;#39;api_pool&amp;#39;**
      )

      task_z = DummyOperator(
          task_id=&amp;#39;end&amp;#39;
      )

      task_w &amp;gt;&amp;gt; [task_x, task_y] &amp;gt;&amp;gt; task_z&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 과정에서 &lt;code&gt;task_w&lt;/code&gt; 는 &lt;code&gt;default_pool&lt;/code&gt; 을 점유하므로, &lt;code&gt;task_a&lt;/code&gt; , &lt;code&gt;task_b&lt;/code&gt; , &lt;code&gt;task_c&lt;/code&gt; 와는 별개로 실행될 수 있습니다. &lt;/p&gt;
&lt;p&gt;그러나 &lt;code&gt;task_x&lt;/code&gt; 와 &lt;code&gt;task_y&lt;/code&gt; 는 각각 2와 1의 &lt;code&gt;priority_weight&lt;/code&gt; 가 설정되어 있습니다. 따라서 &lt;code&gt;pool_priority_dag&lt;/code&gt; DAG의 모든 Task 중에 하나라도 종료되면 &lt;code&gt;task_x&lt;/code&gt; 와 &lt;code&gt;task_y&lt;/code&gt; 가 실행될 수 있습니다. &lt;/p&gt;
&lt;p&gt;단, &lt;code&gt;task_x&lt;/code&gt; 와 &lt;code&gt;task_y&lt;/code&gt; 사이에서도 가중치의 차이가 존재한다는 점을 주목해야 합니다. &lt;code&gt;task_x&lt;/code&gt; 의 가중치가 &lt;code&gt;task_y&lt;/code&gt; 에 비해 상대적으로 높으므로, &lt;code&gt;pool_priority_dag&lt;/code&gt; 의 Task 중 하나가 끝나면 &lt;code&gt;task_x&lt;/code&gt; 가 그 자리를 차지하게 됩니다. 이후 &lt;code&gt;pool_priority_dag&lt;/code&gt; 의 나머지 두 Task 중 하나 또는 &lt;code&gt;task_x&lt;/code&gt; 가 완료되면 그 남은 자리를 &lt;code&gt;task_y&lt;/code&gt; 가 점유하게 됩니다.&lt;/p&gt;
&lt;p&gt;이런 방식으로 API Endpoint에 동시에 최대 3개까지만 요청을 보낼 수 있도록 제어할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이러한 방식으로 여러 DAG간의 실행 제어도 가능하다는 것을 살펴볼 수 있었습니다. 물론, DagRun 자체에 동시성을 제어하고자 한다면, 앞서 소개드린 &lt;code&gt;max_active_runs&lt;/code&gt; 또는 &lt;code&gt;core.max_active_runs_per_dag&lt;/code&gt; 를 확인하는 것이 필요합니다.&lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/concepts/pools.html&quot;&gt;https://airflow.apache.org/docs/apache-airflow/stable/concepts/pools.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.astronomer.io/guides/airflow-pools/&quot;&gt;https://www.astronomer.io/guides/airflow-pools/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Airflow</category>
      <category>Airflow</category>
      <category>Data Engineering</category>
      <category>pool</category>
      <category>workflow</category>
      <category>데이터 엔지니어링</category>
      <category>에어플로</category>
      <category>워크플로</category>
      <category>풀</category>
      <author>wookiist</author>
      <guid isPermaLink="true">https://wookiist.tistory.com/171</guid>
      <comments>https://wookiist.tistory.com/171#entry171comment</comments>
      <pubDate>Sat, 9 Jul 2022 11:27:21 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] Dynamic Task Mapping (동적 태스크 매핑)</title>
      <link>https://wookiist.tistory.com/170</link>
      <description>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;최근 사내에서 사용하는 몇몇 Airflow 클러스터 관리를 담당하게 되면서 Airflow 2.3.0으로의 버전업을 진행하게 되었습니다. 사실 이전에 다뤘던 두 포스트도 그 일환으로 작성했던 내용이네요 ㅎㅎ&lt;/p&gt;
&lt;p&gt;Airflow 2.3.0 업그레이드에는 나름 큰(?) 변경 사항과 추가 사항이 있습니다. 그동안 사용해왔던 TreeView를 대체하는 GridView의 도입, LocalKubernetesExecutor의 도입, 메타데이터 DB에 쌓여만 가던 오래된 데이터를 지워주는 &lt;code&gt;airflow db clean&lt;/code&gt; 명령어 등, 각각 하나의 글로 작성해도 충분할만큼 중요한 신규 기능, 변경점들입니다.&lt;/p&gt;
&lt;p&gt;그중에서도 가장 주목 받았던 신규 기능이라고 한다면, &lt;code&gt;Dynamic Task Mapping&lt;/code&gt; 을 꼽을 수 있겠네요! 왜 그런지는.. 이제부터 파악해보려구요 ㅋㅋㅋ&lt;/p&gt;
&lt;p&gt;Dynamic Task Mapping이 도입되면서 런타임에 동적으로 병렬 Task를 생성하는 DAG를 작성할 수 있게 되었습니다! Airflow 2.3.0 이전까지는 DAG가 파싱되는 시점에만 동적으로 Task를 생성할 수 있었습니다. 말인 즉슨, DAG 파싱 시점 이후에 변경된 외부 요소가 있다면, 이건 반영되지 않는다는 것이죠. 이런 어려움을, Dynamic Task Mapping과 함께라면 해결할 수 있습니다. DAG가 현재 런타임 시점의 환경을 기반으로 Task를 생성하게 되었기 때문입니다.&lt;/p&gt;
&lt;p&gt;사실 여기까지만 들어도, 왜 가장 주목받았던 신규 기능인지는 이미 파악이 된 거 같습니다. 그럼 좀 더 자세히 알아볼까요!&lt;/p&gt;
&lt;h1&gt;Dynamic Task 컨셉&lt;/h1&gt;
&lt;p&gt;Airflow의 Dynamic Task Mapping 기능은 MapReduce 모델을 기반으로 하고 있습니다. &lt;del&gt;최근 들어 Hadoop 스터디를 하면서도 MapReduce를 마주쳤는데, 여기서도 보게 되니 반갑네요!&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;Map은 일련의 Input Data를 받아서, 각각에 대해 단일 작업을 생성합니다. Reduce는 Map 작업을 통해 만들어진 결과물을 가지고 연산을 수행합니다. Map은 필수적으로 작업이 이뤄져야 하지만, Reduce는 그렇지는 않습니다. 흔히 Map은 Transformation, Reduce는 Action으로 표현합니다. 말 그대로, 데이터를 연산하기에 좋은 형태로 변형하는 작업이 Map이고, 그렇게 만들어진 형태의 데이터를 갖고 연산하는 것을 Reduce라고 보면 됩니다. &lt;/p&gt;
&lt;p&gt;Airflow에 연관지어 이야기해보면 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Map : 런타임에 DAG가, 몇몇 입력 파라미터를 기반으로 임의의 개수만큼 병렬 Task를 생성할 수 있음을 의미합니다.&lt;/li&gt;
&lt;li&gt;Reduce : 그리고 만약 필요하다면, Map 과정에서 만들어진 결과물을 기반으로 병렬적으로 Mapping 된 단일 Task의 다운스트림을 생성할 수 있음을 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dynamic Task Mapping의 “map” 부분을 구현하기 위해 Airflow Task는 두 가지 새로운 함수를 제공합니다. 여러분이 Map 하고자 하는 Task엔, 모든 operator 파라미터가, 다음 두 함수 중 하나를 통해 반드시 전달되어야 합니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;expand()&lt;/code&gt; : 이 함수는 mapping하고자 하는 파라미터를 전달합니다. 각 입력 값에 대해 별도의 병렬 Task가 생성됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;partial()&lt;/code&gt; : 이 함수는 &lt;code&gt;expand()&lt;/code&gt; 함수로 생성된 모든 mapped Task에 일정하게 유지되는 파라미터를 전달합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다음 코드에 정의된 Task는 동적으로 3개의 Task를 실행하기 위해 위 두 가지 함수를 모두 사용하는 예제입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@task
def add(x: int, y: int):
    return x+y

added_values = add.partial(y=10).expand(x=[1,2,3])

# add 함수의 결과는 다음처럼 expand 됩니다.
# add(x=1, y=10)
# add(x=2, y=10)
# add(x=3, y=10)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 &lt;code&gt;expand()&lt;/code&gt; 함수는 세 개의 mapped &lt;code&gt;add&lt;/code&gt; Task를 생성합니다. 그리고 그 세 개의 Task는 각각 &lt;code&gt;x&lt;/code&gt; 에 대한 값으로 입력 리스트에 있는 값을 하나씩 갖도록 합니다. (예제의 경우, 각각 1, 2, 3을 하나씩 갖는다고 보면 됩니다.) 그리고 &lt;code&gt;partial()&lt;/code&gt; 함수는 세 개의 Task에 대해 남은 값인 &lt;code&gt;y&lt;/code&gt; 값을 지정해주고 있습니다. &lt;/p&gt;
&lt;p&gt;이렇게 mapped Task를 가지고 작업할 땐, 몇 가지 명심해야 할 점이 있습니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;이제 업스트림 Task의 결과를 Mapped Task에 대한 입력 값으로 사용할 수 있습니다 (이건 Dynamic Task Mapping 기능이 엄청난 기능임을 보여주는 이유이기도 합니다) 업스트림 Task는 반드시 &lt;code&gt;dict&lt;/code&gt; 또는 &lt;code&gt;list&lt;/code&gt; 의 형태로 결과를 리턴해야 합니다. 만약 decorator task가 아닌 구 operator를 사용한다면, mapping 값은 XCom에 반드시 보관해야 합니다. (아래 &lt;code&gt;이전 스타일의 operator ...&lt;/code&gt; 내용에서 소개된 예제를 참조해주세요)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;단일 파라미터 뿐만 아니라, &lt;code&gt;expand()&lt;/code&gt; 를 위해서 여러 개의 파라미터를 전달할 수도 있습니다. 이렇게 하면, 각 파라미터의 조합으로 mapped Task을 호출하는 “외적(cross product)” 을 생성하는 결과를 만듭니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  @task
  def add(x: int, y: int):
      return x + y

  added_values = add.expand(x=[2, 4, 8], y=[5, 10])

  # 이렇게 하면 add 함수가 다음처럼 호출되는 결과를 만듭니다.
  # add(x=2, y=5)
  # add(x=2, y=10)
  # add(x=4, y=5)
  # add(x=4, y=10)
  # add(x=8, y=5)
  # add(x=8, y=10)&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;mapped Task의 결과물을 다운스트림 mapped Task의 입력값으로 활용할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;아무 Task Instance도 생성하지 않는 mapped Task가 있을 수도 있습니다. (예를 들어, mapping 값을 생성하는 업스트림 Task가 빈 리스트를 리턴했을 때) 이 경우에는, mapped Task가 &lt;code&gt;skipped&lt;/code&gt; 로 마킹되며, 다운스트림 Task는 설정된 Trigger Rule에 따라 진행됩니다. (아무런 설정을 하지 않았다면, 다운스트림 또한 &lt;code&gt;skipped&lt;/code&gt;로 마킹됩니다.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이전 스타일의 operator 에 대해서도 &lt;code&gt;partial&lt;/code&gt; 과 &lt;code&gt;expand&lt;/code&gt; 을 사용할 수 있습니다. 다만 일부 파라미터는 mappable 하지 않으므로, 반드시 &lt;code&gt;partial()&lt;/code&gt; 함수를 통해 전달되어야 합니다. 예를 들어, &lt;code&gt;task_id&lt;/code&gt; , &lt;code&gt;pool&lt;/code&gt; 그리고 대부분의 &lt;code&gt;BaseOperator&lt;/code&gt; 에 사용되는 인자들이 이에 해당합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  BashOperator.partial(task_id=&amp;quot;bash&amp;quot;, do_xcom_push=False).expand(
      bash_command=[&amp;quot;echo 1&amp;quot;, &amp;quot;echo 2&amp;quot;]
  )&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Use Case&lt;/h1&gt;
&lt;p&gt;Dynamic Task Mapping 은 그 활용도가 무궁무진합니다. 대표적으로는 ELT 또는 ML Ops 등에 활용할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;ELT&lt;/h2&gt;
&lt;p&gt;예를 들어, S3 파일에서 데이터를 추출하고, 이 데이터를 Snowflake에 로드하고, Snowflake는 연산을 수행해서 데이터를 변환하는 ELT 파이프라인이 있다고 가정할 때, S3 버킷에는 얼마나 많은 파일이 존재하는지 알 수 없습니다. 파일의 개수는 그날 그날 다르기 때문입니다. 이런 상황에서, 런타임 시점에 파일의 개수만큼 동적으로 Task를 만들어줄 수 있으면 훨씬 더 쉬운 파이프라인 관리, atomicity를 제공해줄 수 있겠죠! 이런 상황에서 Dynamic Task Mapping이 사용될 수 있습니다.&lt;/p&gt;
&lt;h2&gt;ML Ops&lt;/h2&gt;
&lt;p&gt;ML Ops에선 동적으로 변하는 컴포넌트가 포함되는 경우가 많습니다. 다음의 사례에서 유용하게 사용할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서로 다른 모델을 학습시키기 : 모델마다 별도의 DAG를 작성할 필요가 없습니다. 모델 리스트가 주기적으로 변경되더라도, Dynamic Task Mapping을 활용하면, 사용자가 개입하지 않아도, 각 모델마다 작업을 진행해줍니다. 그리고 모델 리스트에서 빠진 과거의 모델에 대해서도 히스토리를 유지해줍니다.&lt;/li&gt;
&lt;li&gt;모델에 대해 Hyperparameter 학습 진행하기 : 최상의 파라미터 집합을 통해 얻은 결과물을 배포하기 전에 하이퍼파라미터 튜닝을 진행하고 싶은 단일 모델이 있을 때 활용할 수 있습니다. Dynamic Task Mapping을 활용하게 되면, 런타임 시점에 외부 시스템으로부터 필요한 파라미터를 가져와 활용할 수 있기 때문에 작업을 유연하게 진행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;고객별로 다른 모델 생성하기 : 각 고객마다 별도로 학습을 진행해야 하는 모델이 있습니다. 특히 고객 리스트는 자주 변경될 수 있고, 이전 고객에 대해서도 그 히스토리를 유지해야 합니다. Dynamic Task Mapping 이전에는 삭제된 고객에 대해서 히스토리를 유지할 방법이 없었습니다. 하지만 이제는 매번 동적으로 고객 리스트를 처리할 수 있게 되어, 단일 DAG 하나만으로도 학습을 진행할 수 있고, 매번 바뀌는 고객 리스트에서 히스토리 유지도 가능하게 되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제 DAG&lt;/h1&gt;
&lt;p&gt;이번에는 직접 DAG를 작성해서 어떻게 실행되는지 한번 살펴보겠습니다. 아래 예제는 위에서 활용했던 &lt;code&gt;add&lt;/code&gt; Task에 &lt;code&gt;print&lt;/code&gt; Task를 연결해서 만들어진 value list를 로깅하는 단순한 DAG 코드입니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging

from datetime import datetime

from airflow import DAG
from airflow.decorators import task

with DAG(dag_id=&amp;quot;sample_repeated_mapping&amp;quot;,
         start_date=datetime(2022, 6, 4),
         catchup=False
        ) as dag:

    @task
    def add(x: int, y: int):
        return x + y

    @task
    def print_x(x: list):
        logging.info(x)

    added_values = add.expand(x=[2, 4, 8], y=[5, 10])
    print_x(added_values)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 DAG는 Airflow UI에서 어떻게 표현될까요? Graph View에서 보면, 모든 mapped Task는 &lt;code&gt;task_id&lt;/code&gt; 옆에 &lt;code&gt;[ ]&lt;/code&gt; 처럼 대괄호 쌍이 표시됩니다. 아래 이미지에서 보면 &lt;code&gt;add []&lt;/code&gt; 로 나타나고 있습니다. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/wookiist/tistory-images/blob/main/2022/06/06/01.png?raw=true&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;이 DAG를 실행하면, 대괄호 안에는 동적으로 생성된 mapped Task Instacne의 개수가 표시됩니다. 아래는 이미지를 보면 &lt;code&gt;add [6]&lt;/code&gt; 처럼 나타난 것을 볼 수 있습니다. 이 개수는 동적으로 생성되기 때문에 매번 달라질 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/wookiist/tistory-images/blob/main/2022/06/06/02.png?raw=true&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;p&gt;또한 해당 Task를 눌러보면, 각각의 mapped Task Instance를 살펴볼 수 있도록 아래처럼 하나씩 선택해서 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/wookiist/tistory-images/blob/main/2022/06/06/03.png?raw=true&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;p&gt;Airflow 2.3.0 부터 변경된 Grid View에서도 다음처럼 mapped Instance가 무엇인지 볼 수 있습니다. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/wookiist/tistory-images/blob/main/2022/06/06/04.png?raw=true&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;p&gt;mapped Task의 Detail Panel을 보게 되면 mapped Task Instance의 리스트도 한 눈에 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/wookiist/tistory-images/blob/main/2022/06/06/05.png?raw=true&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;p&gt;최근 들어 Airflow 2.3.x 를 다룰 일이 많네요. 그리고 새로운 기능에 대해서도 하나하나 알아가보는 중에 만나게 된 Dynamic Task Mapping에 대해 알아보는 글을 작성해보았습니다. 추후 Airflow 2.3.0 릴리즈를 소개하는 내용으로 이어가겠습니다.&lt;/p&gt;
&lt;p&gt;만약 이 글이 도움이 되셨다면 글 좌측 하단의 하트❤를 눌러주시면 감사하겠습니다.&lt;/p&gt;
&lt;p&gt;혹시라도 글에 이상이 있거나, 이상한 번역이 있거나, 이해가 가지 않으시는 부분, 또는 추가적으로 궁금하신 내용이 있다면 주저 마시고 댓글 을 남겨주세요! 빠른 시간 안에 답변을 드리겠습니다  &lt;/p&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.astronomer.io/guides/dynamic-tasks&quot;&gt;https://www.astronomer.io/guides/dynamic-tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/2.3.0/concepts/dynamic-task-mapping.html&quot;&gt;https://airflow.apache.org/docs/apache-airflow/2.3.0/concepts/dynamic-task-mapping.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Airflow</category>
      <category>2.3.0</category>
      <category>Airflow</category>
      <category>data</category>
      <category>Data Engineering</category>
      <category>Dynamic Task Mapping</category>
      <category>데이터 엔지니어링</category>
      <category>동적 태스크 매핑</category>
      <category>에어플로</category>
      <category>에어플로우</category>
      <author>wookiist</author>
      <guid isPermaLink="true">https://wookiist.tistory.com/170</guid>
      <comments>https://wookiist.tistory.com/170#entry170comment</comments>
      <pubDate>Mon, 6 Jun 2022 17:51:32 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] Sensor를 정리해보자</title>
      <link>https://wookiist.tistory.com/169</link>
      <description>&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;최근 들어 Airflow를 적극적으로 다루는 일이 많아지다보니, 여러 요구사항을 만나게 되는데요. Airflow 자체적으로 제공하지 않는 스케줄링 처리, 특정 파일 및 조건을 만족할 때까지 대기하는 구간 등을 정의하게 되었습니다. 전자는 간단한 Operator를 구현해, 정공법은 아니지만 Workaround 느낌으로 파훼했고, 후자는 이번에 알아볼 Sensor로 해결할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;Apache Airflow Sensor는 어떤 사건이 발생할 때 까지 기다리도록 설계된 특수한 종류의 오퍼레이터입니다. 실행된 Sensor는 특정 조건을 만족하면, 성공으로 마킹되며 이후 다운스트림 태스크를 실행합니다.  적절하게 사용되면, DAG를 좀 더 Event-Driven하게 작성할 수 있도록 도와줍니다.&lt;/p&gt;
&lt;p&gt;Sensor가 필요한 경우를 한 번 예로 들어보겠습니다. 데이터 파이프라인이 시작하는 일반적인 조건은 파이프라인에 사용할 새로운 데이터가 도착했을 경우입니다. 데이터의 입수 과정부터 직접 담당하고 있다면 정해진 시간마다 특정 기간 동안 쌓인 데이터를 가지고 파이프라인을 시작하면 됩니다. 하지만 타사, 타부서로부터 데이터를 받아 처리하는 입장이라면 어떨까요? 그리고 그 데이터가 매번 같은 시간에 입수되는 것이 아니라면요? 이럴 경우엔, 데이터 입수 시간의 마지노선을 기준으로 파이프라인을 스케줄링할 수도 있습니다. 하지만, 이렇게 할 경우, 데이터가 빨리 입수될 수록 시간적으로 손해를 보게 됩니다. &lt;/p&gt;
&lt;p&gt;바로 이럴 때, 특정 조건을 만족하는 경우에 파이프라인을 실행하도록 하는 Sensor의 도움을 받을 수 있습니다. &lt;/p&gt;
&lt;h1&gt;Sensor Basic&lt;/h1&gt;
&lt;p&gt;Sensor는 개념적으로 간단하게 이해할 수 있습니다. 특정 주기 동안 어떤 조건을 만족했는지 확인하는 오퍼레이터입니다. 조건을 만족하면, 해당 태스크를 성공을 마킹하고, 이후의 다운스트림 태스크를 실행합니다. 만약 조건이 아직 만족되지 않았다면, Sensor는 다시 확인하기까지의 또다른 주기를 기다립니다. &lt;/p&gt;
&lt;p&gt;모든 Sensor들은 &lt;code&gt;BaseSensorOperator&lt;/code&gt;를 상속하고 있으며, 다음의 파라미터를 갖고 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mode&lt;/code&gt; : Sensor의 동작 방식을 결정합니다. 다음의 두 가지 방식이 있습니다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;poke&lt;/code&gt; : 아무런 설정도 하지 않는다면 기본적으로 &lt;code&gt;poke&lt;/code&gt; 방식으로 동작합니다. &lt;code&gt;poke&lt;/code&gt; 를 사용하면, Sensor는 해당 Sensor의 전체 실행 시간동안 worker 슬롯을 점유하며, poke 사이에는 sleep 상태로 존재합니다. 이 &lt;code&gt;poke&lt;/code&gt; 방식은 Sensor가 짧은 실행시간을 가질 것으로 예상할 때 사용하기에 적절합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reschedule&lt;/code&gt; : 이 방식을 사용하면, 조건을 만족하지 않았을 때 Sensor는 worker 슬롯을 놓아주고, 다음 번 확인을 위해 reschedule 됩니다. 이 방식은 Sensor가 긴 실행시간을 가질 것으로 예상될 때 적절합니다. worker 슬롯을 놓아주기 때문에 다른 태스크를 실행할 수 있고, 자원을 덜 소모하기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;poke_interval&lt;/code&gt; : &lt;code&gt;poke&lt;/code&gt; 방식을 사용할 때, 이 옵션은 Sensor가 조건을 확인하는 다음번 주기(초)를 결정합니다. 기본 값은 30초 입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exponential_backoff&lt;/code&gt; : &lt;code&gt;True&lt;/code&gt; 로 설정하면, &lt;code&gt;poke&lt;/code&gt; 모드에서 이전 poke와 다음 poke 사이의 간격을 지수적으로 증가시킵니다. Kubernetes의 CrashLoopBackoff와 유사하네요.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timeout&lt;/code&gt; : Sensor가 조건을 확인을 시도할 최대 시간(초)을 설정합니다. 이 시간까지도 조건을 만족하지 못하면, 해당 태스크는 실패합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;soft_fail&lt;/code&gt; : &lt;code&gt;True&lt;/code&gt; 로 설정하면, 해당 태스크가 &lt;code&gt;timeout&lt;/code&gt; 이 나더라도 실패가 아닌 스킵으로 마킹됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;일반적으로 많이 사용되는 Sensors&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;FileSensor&lt;/code&gt; : 특정 파일이 존재하는지 확인하고 파일이 있으면 true를, 그렇지 않다면 false를 리턴하는 Sensor입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;PythonSensor&lt;/code&gt; :  python_callable로 설정한 함수가 &lt;code&gt;True&lt;/code&gt;를 반환할 때까지 기다리는 Sensor입니다. DAG 내에서 복잡한 조건을 구현하고 싶을 때 유용합니다. FileSensor만 사용한다면 파일 유무만을 조건으로 활용하게 되지만, PythonSensor를 이용하면, 파일의 유무와 더불어서 특정 조건이 만족되는지도 확인이 가능합니다. 다음처럼 True, False를 리턴하는  python 함수를 하나 작성해서 사용하면 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  from airflow.sensors.python import PythonSensor
  ...
  def check_condition():
      if ...:
          return True
      return False

  python_sensor_task = PythonSensor(python_callable=check_condition,
                                    task_id=&amp;#39;python_sensor_task&amp;#39;,
                                    poke_interval=10,
                                    dag=dag,
                                  )&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;DateTimeSensor&lt;/code&gt; : 지정한 날짜와 시간이 지나기를 기다리는 Sensor입니다. 동일한 DAG 내에서 다른 태스크를 다른 시간에 실행하려는 경우에 유용합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ExternalTaskSensor&lt;/code&gt; : Airflow 태스크가 완료되기를 끼다리는 Sensor입니다. 같은 Airflow 환경에서 DAG 간 의존성을 정의해야할 때 유용합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;HttpSensor&lt;/code&gt; : API requests가 성공하는 것을 보장해야 할 때 유용한 Sensor입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;SqlSensor&lt;/code&gt; : 데이터가 SQL 테이블에 존재할 때까지 기다리는 Sensor입니다. 데이터가 DB에 보관되었을 때 해당 데이터를 처리하는 DAG를 작성해야 할 때 유용합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Sensor를 사용하기 위한 가장 효과적인 방법&lt;/h1&gt;
&lt;p&gt;Sensor는 구현하기 쉽지만, 최고의 Airflow 경험을 획득하기 위해선 몇 가지 기억해야 하는 사항이 있습니다. 다음의 팁을 따르면, Sensor를 사용함으로써 발생할 수 있는 성능 이슈를 피할 수 있을 겁니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;언제나&lt;/strong&gt; 의미있는 &lt;code&gt;timeout&lt;/code&gt; 파라미터를 Sensor에 설정하세요. 이 파라미터의 기본값은 7일입니다. 7일은 Sensor가 실행되는 시간치고는 긴 편이죠. Sensor를 정의할 땐, 사용 목적과 Sensor가 얼마나 기다리게 될 지 등등을 모두 고려해서 Sensor의 Timeout을 적절하게 정의해야 합니다.&lt;/li&gt;
&lt;li&gt;긴 시간동안 실행될 거 같은 Sensor라면, &lt;code&gt;reschedule&lt;/code&gt; 방식을 사용해서 Sensor가 Worker 슬롯을 지속적으로 점유하지 않도록 하세요. 이렇게 하면, Sensor 들이 가용한 Worker 슬롯을 모두 점거해버리는 데드락 현상을 피할 수 있습니다. 다만, 다음의 경우는 예외입니다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;poke_interval&lt;/code&gt; 이 약 5분보다도 짧다면, &lt;code&gt;poke&lt;/code&gt; 방식을 활용하세요. 이런 상황에서 &lt;code&gt;reschedule&lt;/code&gt; 방식을 사용하는 것은 스케줄러에 부하를 줄 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사용 사례에 적절한 &lt;code&gt;poke_interval&lt;/code&gt; 값을 설정하세요. 예를 들어, 기다려야 하는 시간이 30분이라는 걸 이미 알고 있을 때, (기본값인) 30초마다 조건을 확인할 필요가 없는 것처럼 말이죠.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Sensor Deadlock&lt;/h3&gt;
&lt;p&gt;실행중인 태스크의 조건이 true가 될 때까지 다른 태스크들이 대기하게 되므로, 모든 슬롯이 데드락 상태가 됩니다.&lt;/p&gt;
&lt;h1&gt;Deferrable Operators&lt;/h1&gt;
&lt;p&gt;Asynchronous Operators라고도 불리는 Deferrable Operators는 Operator나 Sensor가 자신들이 실행되는 동안 모든 Worker 슬롯을 점거해버리는 상황을 막기 위해 Airflow 2.2에서 등장했습니다. Airflow 2.2.4에서 Deprecate된 Smart Sensor와 유사한 문제를 해결하고 있지만, 그 쓰임새는 더 광범위합니다. &lt;/p&gt;
&lt;p&gt;DAG를 작성하는 사람들에게 빌트인 deferrable operator를 사용하는 것은, 다른 일반적인 operator를 활용하는 것과 다르지 않습니다. 단지, 스케줄러와 함께 &lt;code&gt;Triggerer&lt;/code&gt; 프로세스가 실행되고 있음을 보장하기만 하면 됩니다. 현재 오픈소스 Airflow 진영 바깥에서는 &lt;code&gt;DateTimeSensorAsync&lt;/code&gt; 와 &lt;code&gt;TimeDeltaSensorAsync&lt;/code&gt; 가 활용되고 있는데요, 더 많은 deferrable operators는 향후 출시될 Airflow 버전에서 추가될 것으로 기대하고 있습니다. &lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.astronomer.io/guides/what-is-a-sensor/&quot;&gt;https://www.astronomer.io/guides/what-is-a-sensor/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.manning.com/books/data-pipelines-with-apache-airflow&quot;&gt;Data Pipelines with Apache Airflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;p&gt;Airflow를 막 사용하기 시작한지 얼마 되지 않은 유저의 입장에서, Sensor를 알아보는 글을 번역 + 추가 소개를 해보았습니다. TimeSensor가 있는 줄은 몰랐는데, 이번에 알게 되면서 요것도 한번 사용해볼까 고민중이네요!&lt;/p&gt;
&lt;p&gt;만약 이 글이 도움이 되셨다면 글 좌측 하단의 하트❤를 눌러주시면 감사하겠습니다.&lt;/p&gt;
&lt;p&gt;혹시라도 글에 이상이 있거나, 오역, 이상한 번역이 있거나, 이해가 가지 않으시는 부분, 또는 추가적으로 궁금하신 내용이 있다면 주저 마시고 댓글 을 남겨주세요! 빠른 시간 안에 답변을 드리겠습니다  &lt;/p&gt;</description>
      <category>IT/Airflow</category>
      <category>Airflow</category>
      <category>Deferrable Operator</category>
      <category>operator</category>
      <category>sensor</category>
      <category>센서</category>
      <category>에어플로</category>
      <category>오퍼레이터</category>
      <author>wookiist</author>
      <guid isPermaLink="true">https://wookiist.tistory.com/169</guid>
      <comments>https://wookiist.tistory.com/169#entry169comment</comments>
      <pubDate>Sun, 22 May 2022 10:12:58 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] Airflow DAG Serialization (직렬화)</title>
      <link>https://wookiist.tistory.com/168</link>
      <description>&lt;h1&gt;직렬화(Serialization)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Airflow의 DAG 직렬화를 알아보기 전에, 직렬화가 무엇인지부터 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 어떤 객체 데이터를 가지고 저장하거나 통신한다고 가정해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 실행중인 프로세스상에서 데이터는 연속적이지 않게 메모리에 퍼져 있습니다. 물론 프로세스를 메모리에 연속된 주소로 할당하는 기법도 있긴 하지만, 단편화 문제도 있고, 가장 크게는 메모리 크기를 넘어서는 프로세스는 실행할 수 없다는 치명적인 문제가 있어서 사용하지 않고 있으니 요 친구는 논외로 하겠습니다. 그러니 앞서 말씀드린 것처럼, 객체 데이터는 비연속적으로 퍼져 있다고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 객체 데이터를 가지고 통신하거나 저장하려면, 그 객체 데이터 그대로를 사용해서는 불가능합니다. 메모리 주솟값 등등 고려해야 할 것도 너무 많고, 이 형태 그대로는 다른 환경(저장 공간, 다른 프로세스)에선 사용할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 직렬화(Serialization)가 등장합니다. 직렬화는 데이터 구조나 오브젝트의 상태를, 동일하거나 다른 환경에 &lt;b&gt;저장하고 나중에 재구성할 수 있는 포맷으로 변환&lt;/b&gt;하는 과정입니다. 좀 더 프로그래밍 언어가 하는 일에 가깝게 풀자면, &lt;b&gt;객체의 내용을 바이트 단위 배열로 변환하여, 파일 또는 네트워크를 통해 스트림(송수신)이 가능하게 하는 것&lt;/b&gt;을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 직렬화된 데이터를 수신 또는 저장한 후에, 이를 객체로 다시 사용하려면, 역직렬화(Deserialization) 과정을 거쳐주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여담이지만, 직렬화는 마샬링(Marshaling) 한다고도 표현합니다. Golang에서는 이 용어로 사용되고 있었던 게 생각나네요. (역직렬화는 Unmarshaling)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이제 Airflow DAG를 직렬화하는 이유는 무엇인지 알아보겠습니다.&lt;/p&gt;
&lt;h1&gt;Airflow DAG 직렬화&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAG 직렬화가 없고, Meadata DB를 사용하지 않던 때에는, 아래 좌측 이미지처럼, Webserver와 Scheduler 모두가 DAG 파일에 직접 접근해서 DAG를 파싱해와야 했습니다. 이는 같은 작업(DAG 파싱)을 반복해야만 하는 문제가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 이러한 DAG 파싱 작업은 Webserver가 새로 시작되는 경우에, 다시 수행해야 하므로(파싱한 DAG를 들고 있어야 하기 때문 = Stateful) Webserver의 로딩 타임과 메모리를 키우는 주요 원인이기도 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/wookiist/til.wookiist.dev/blob/master/static/img/dag_serialization.png?raw=true&quot; alt=&quot;https://github.com/wookiist/til.wookiist.dev/blob/master/static/img/dag_serialization.png?raw=true&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하고자, Airflow에서도 1.10.7 이상부터 DAG를 직렬화해서 이용하는 것을 지원하고 있습니다. 특히 Airflow 2.0.0 이상부터는 DAG 직렬화 옵션은 비활성화할 수 없습니다. 이는 Airflow Webserver를 Stateless하게 만듭니다. 여담으로, Airflow 2.0.0 이상부터는 scheduler도 일관성을 위해 직렬화된 DAG를 사용하여 스케줄링을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAG를 직렬화하게 되면, 이젠 Webserver를 DAG 파싱 작업에서 분리하여 매우 가볍게 만들 수 있습니다. DAG 파싱 작업은 Scheduler에서만 수행하게 됩니다. Scheduler에서 DagFileProcessorProcess가 DAG 파일을 파싱하여 JSON 형식으로 직렬화하고, 이를 Metadata DB에 SerializedDagModel 모델로 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면, webserver는 DAG 파일을 다시 파싱하는 대신에, JSON으로 직렬화된 DAG를 읽고, 이를 역직렬화해서 DagBag을 생성한 다음 이를 가지고 UI에 나타내 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Scheduler도, 스케줄링을 수행하는 데에 실제 DAG 파일이 필요하지 않습니다. Airflow 2.0.0부터는 DAG를 스케줄링하는 데에 필요한 정보도 포함해서 DAG를 직렬화하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAG 직렬화를 사용하게 됨으로써 얻는 가장 큰 이점 중 하나는 Webserver가 시작될 때, 매번 전체 DagBag을 로드하는 대신에, 필요한 DAG에 대해서만 직렬화된 DAG 테이블에서 로드한다는 점입니다. 이는 곧, Webserver의 시작 시각과 메모리를 줄이는 데에 도움이 되며, 특히 많은 DAG가 존재할 때 눈에 띄는 향상을 보여줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;관련 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAG 직렬화와 관련된 설정을 변경하려면 &lt;code&gt;airflow.cfg&lt;/code&gt; 에서 다음 key를 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;[core]

min_serialized_dag_update_interval = 30
min_serialized_dag_fetch_interval = 10
max_num_rendered_ti_fields_per_task = 30
compress_serialized_dags = False&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;min_serialized_dag_update_interval&lt;/code&gt; : 이 옵션은 DB에 저장된 직렬화된 DAG가 업데이트되어야 하는 최소 주기(초)를 설정합니다. DB 쓰기 속도를 제어하는 데에 도움을 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;min_serialized_dag_fetch_interval&lt;/code&gt; : 이 옵션은 직렬화된 DAG가 Webserver의 DagBag에 이미 로드되어 있을 때, DB에서 다시 가져오는 주기를 설정합니다. 이 값을 높게 설정하면 (당연하게도) DB에 가해지는 부하는 줄어들지만, DAG의 현재 버전이 아닌 이전에 캐싱 된 오래된 버전이 보일 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_num_rendered_ti_fields_per_task&lt;/code&gt; : 이 옵션은 DB에 저장할 Task당 Rendered Task Instance Fields (Template Fields) 의 최대 개수를 설정합니다. (execution_date나, 해당 시점에 입력되는 동적 변수들을 담아 렌더링한 인스턴스를 의미하는 것으로 보입니다)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;compress_serialized_dags&lt;/code&gt; : 이 옵션은 직렬화된 DAG를 DB에 적재할 때 압축 여부를 결정합니다. 이 옵션은 클러스터 내에 매우 큰 DAG들이 존재하는 경우 유용합니다. 다만, 이 옵션이 &lt;code&gt;True&lt;/code&gt; 로 설정되면, DAG 종속성 보기(DAG dependencies view) 가 비활성화됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/dag-serialization.html&quot;&gt;https://airflow.apache.org/docs/apache-airflow/stable/dag-serialization.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://study-for-me.tistory.com/10&quot;&gt;https://study-for-me.tistory.com/10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%A7%81%EB%A0%AC%ED%99%94&quot;&gt;https://ko.wikipedia.org/wiki/직렬화&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lordofkangs.tistory.com/203&quot;&gt;https://lordofkangs.tistory.com/203&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Airflow 2.3.0의 릴리즈 문서를 정리하면서 포스트를 작성하던 와중에 &lt;code&gt;airflow dags reserialize&lt;/code&gt; 커맨드가 새로 추가되었다는 내용을 보게 되었습니다. Serialize에 대해서도 잘 모르는 와중에 Reserialize 내용을 작성할 수는 없겠다 싶어서 간단하게 정리해보았습니다. 추후 Airflow 2.3.0 릴리즈를 소개하는 내용으로 이어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 글이 도움이 되셨다면 글 좌측 하단의 하트❤를 눌러주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 글에 이상이 있거나, 오역, 이상한 번역이 있거나, 이해가 가지 않으시는 부분, 또는 추가로 궁금하신 내용이 있다면 주저하지 마시고 댓글 을 남겨주세요! 빠른 시간 안에 답변을 드리겠습니다  &lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-1jcczpw&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>IT/Airflow</category>
      <category>Airflow</category>
      <category>Dag</category>
      <category>Serialization</category>
      <category>에어플로</category>
      <category>직렬화</category>
      <author>wookiist</author>
      <guid isPermaLink="true">https://wookiist.tistory.com/168</guid>
      <comments>https://wookiist.tistory.com/168#entry168comment</comments>
      <pubDate>Sun, 8 May 2022 10:05:33 +0900</pubDate>
    </item>
  </channel>
</rss>