<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>jyuseo</title>
    <link>https://jyu-seo.tistory.com/</link>
    <description>데이터 서빙알바생 쥬</description>
    <language>ko</language>
    <pubDate>Mon, 6 Apr 2026 06:06:23 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jyu_seo_</managingEditor>
    <item>
      <title>[Airflow] - Airflow</title>
      <link>https://jyu-seo.tistory.com/185</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 데이터엔지니어링의 핵심도구인 'Apache Airflow'에 대한 블로그를 작성해 보려고 합니다. Airflow는 데이터엔지니어(DE)와 데이터 애널리틱스 엔지니어(DAE)라면 반드시 알아야되는 툴중 하나입니다. 데이터 파이프라인을 구축하고 운영하는데 있어 핵심적인 역할을 하며, 복잡한 데이터 워크플로우를 효율적으로 관리할 수 있게 해주는 필수 도구입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Airflow&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Airflow는&lt;b&gt; 'Job Orchestraion tool'&lt;/b&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;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 에어플로우는 확장성도 뛰어납니다. AWS, GCP, Azure등 클라우드 서비스로부터 Hadoop, Spark, Presto 같은 빅데이터 도구까지, 다양한 시스템과 연동을 위한 수많은 플러그인을 제공합니다. 이러한 풍부한 생태계 덕분에 거의 모든 데이터 처리 작업을 에어플로우로 통합할 수 있습니다. 실제 운영 측면에서도 웹 기반의 직관적인 인터페이스를 통해 복잡한 파이프라인 상태를 한눈에 파악할 수 있고, 문제가 발생했을 때 빠르게 대응할 수 있습니다. 특히 실패한 태스크의 자동 재시도나 알림 설정같은 기능들은 운영자의 부담을 크게 줄여줍니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터, 그리고 데이터 파이프라인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;데이터의 종류&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 다루는 데이터는 크게 메타데이터와 이벤트데이터로 나눌 수 있는데요. 그 역할에 따라 여러 종류로 분류할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-04-05 122206.png&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QBqbO/dJMcafe6uYO/uDnoNYasLtV9xkGUYvVVYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QBqbO/dJMcafe6uYO/uDnoNYasLtV9xkGUYvVVYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QBqbO/dJMcafe6uYO/uDnoNYasLtV9xkGUYvVVYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQBqbO%2FdJMcafe6uYO%2FuDnoNYasLtV9xkGUYvVVYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1911&quot; height=&quot;683&quot; data-filename=&quot;화면 캡처 2026-04-05 122206.png&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터 파이프라인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 파이프라인은 데이터가 소스에서 목적지까지 이동하는 전체과정을 의미합니다. 현대의 데이터 파이프라인은 크게 &lt;b&gt;ETL(Extract, Transform, Load) 과 ELT(Extract, Load, Transform) &lt;/b&gt;두가지 방식으로 구분됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ETL은 전통적인 방식으로, 데이터를 추출하여 변환한 후 적재하는 방식입니다. 반면 ELT는 먼저 데이터를 추출하여 적재한 후, 필요에 따라 변환하는 현대적인 접근 방식입니다. 특히 OLAP(Online Analytical Processing)환경에서는 ELT가 선호되는데, 이는 대규모 데이터 웨어하우스의 강력한 처리 능력을 활용할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OLAP 시스템은 복잡한 분석 쿼리를 효율적으로 처리할 수 있도록 설계되었습니다. Snowflake,Redshift,BigQuery 같은 현대적인 데이터 웨어하우스들은 컬럼 기반 저장소와 대규모 병렬 처리를 통해 빠른 분석을 가능하게 합니다. 현대의 데이터 파이프라인은 실시간 처리와 배치 처리를 모두 지원해야 하는데요. 실시간 처리는 Kafka, Spark, Streaming 등을 통해 이루어지며, 배치 처리는 Airflow와 같은 도구를 통해 정기적으로 수행됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실시간 처리 VS 배치 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 데이터 처리 방식은 크게 실시간과 배치로 나눌 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZubHk/dJMcadO1vRA/D4sn5QQktLO9ULUAq9jwe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZubHk/dJMcadO1vRA/D4sn5QQktLO9ULUAq9jwe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZubHk/dJMcadO1vRA/D4sn5QQktLO9ULUAq9jwe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZubHk%2FdJMcadO1vRA%2FD4sn5QQktLO9ULUAq9jwe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;976&quot; height=&quot;548&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 처리는 즉각적인 대응이 필요한 상황에서 필수적입니다. 예를들어, 금융 거래에서 부정거래를 탐지하는 경우, 거래가 발생하는 순간 패턴을 분석하여 이상 징후를 감지해야 합니다. 또한 사용자의 현재 행동에 기반한 실시간 추천 시스템이나 서비스 장애를 감지하는 모니터링 시스템에서도 실시간 처리가 중요한 역할을 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Airflow의 역할&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치 처리 환경에서 Airflow는 데이터 파이프라인의 중추적인 역할을 담당합니다. 현재 데이터 엔지니어링 분야에서 가장 널리 사용되는 워크플로우 관리 도구로 자리 잡았는데, 이처럼 표준이 된 이유는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVYBl0/dJMcafe6u6g/gLimi8v6oCf3ohOj7iJO31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVYBl0/dJMcafe6u6g/gLimi8v6oCf3ohOj7iJO31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVYBl0/dJMcafe6u6g/gLimi8v6oCf3ohOj7iJO31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVYBl0%2FdJMcafe6u6g%2FgLimi8v6oCf3ohOj7iJO31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;455&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 기업들은 Airflow를 다음과 같은 배치 작업에 활용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 웨어하우스 테이블 새로고침(Daily/Weekly)&lt;/li&gt;
&lt;li&gt;비즈니스 인텔리전스 리포트 자동 생성&lt;/li&gt;
&lt;li&gt;데이터 품질 검사 자동화&lt;/li&gt;
&lt;li&gt;ML 모델 정기적인 재학습&lt;/li&gt;
&lt;li&gt;로그 데이터 아카이빙&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 분석 환경에서는 데이터 신선도(Freshness)와 품질(Quality)이 매우 중요한데, Airflow는 이러한 요구사항을 효과적으로 충족시켜 줍니다. 예를 들어, 매일 아침 9시에 전날의 데이터를 집계하고, 품질 체크를 수행한 후 이상이 없다면, BI 대시보드를 갱신하는 일련의 과정을 자동화 할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DAG(Directed Acyclic Graph)이해하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Airflow를 완벽히 이해하기 위해서는 몇 가지 핵심 구성요소를 알아야 하는데요. 먼저 DAG부터 시작해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAG는 쉽게말해 '할일 목록'과 '실행 순서'를 정리해 둔 설계도입니다. &quot;먼저 이 작업을 하고 -&amp;gt; 그다음 저 작업을하고 -&amp;gt; 마지막으로 이걸 하자&quot; 라는 순서를 정의해 놓은것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어, 매일 아침 데이터를 처리 하는 과정을 DAG로 표현하면 이렇습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VhA7I/dJMcaiJAauX/EMvBEH4bLHGswFm0kJdZv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VhA7I/dJMcaiJAauX/EMvBEH4bLHGswFm0kJdZv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VhA7I/dJMcaiJAauX/EMvBEH4bLHGswFm0kJdZv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVhA7I%2FdJMcaiJAauX%2FEMvBEH4bLHGswFm0kJdZv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;155&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;155&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DAG 예시&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n6MpM/dJMcadBtaL6/FWI2GlJ5XqL2zuZk99J6h1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n6MpM/dJMcadBtaL6/FWI2GlJ5XqL2zuZk99J6h1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n6MpM/dJMcadBtaL6/FWI2GlJ5XqL2zuZk99J6h1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn6MpM%2FdJMcadBtaL6%2FFWI2GlJ5XqL2zuZk99J6h1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;822&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;1164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B6AQj/dJMcafznE2z/kj4zVJkOaVsJbpiWSWQqJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B6AQj/dJMcafznE2z/kj4zVJkOaVsJbpiWSWQqJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B6AQj/dJMcafznE2z/kj4zVJkOaVsJbpiWSWQqJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB6AQj%2FdJMcafznE2z%2Fkj4zVJkOaVsJbpiWSWQqJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1278&quot; height=&quot;1164&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;1164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시를 살펴보면, 데이터 파이프라인의 기본적인 구조를 이해할수 있습니다. DAG의 이름을 정하고, 실행 시작일과 주기를 설정하며, 필요한 작업들을 Task로 정의하고 있습니다. 이 예시에서는 데이터를 추출하고 변환하는 두 가지 Task를 만들었는데, 이는 PythonOperator를 사용해 구현했습니다. Task들 간의 실행 순서는 연산자로 정의하고 있습니다. 이러한 Task와 Operator는 뒤에서 더 자세히 다뤄보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Task란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task는 DAG 내에서 실행되는 개별 작업의 단위입니다. 하나의 DAG는 여러개의 Task로 구성되며, 각 Task는 특정한 작업을 수행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;1574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zxdlF/dJMcaf0puUT/eZEkGCp4x3lZyGC6KKaZR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zxdlF/dJMcaf0puUT/eZEkGCp4x3lZyGC6KKaZR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zxdlF/dJMcaf0puUT/eZEkGCp4x3lZyGC6KKaZR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzxdlF%2FdJMcaf0puUT%2FeZEkGCp4x3lZyGC6KKaZR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1082&quot; height=&quot;1574&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;1574&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 다이어그램은 하나의 DAG 안에서 여러 Task가 어떻게 협력하는지 보여줍니다. 각각의 네모 박스가 하나의 Task이며, 화살표는 Task간의 의존성을 나타냅니다. Task들의 의존성은 데이터 파이프라인에서 매우 강력한 기능을 제공합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;병렬 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 독립적인 Task들은 동시에 실행될 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AoqJu/dJMcaibOg6K/MhZCBEkHBI5CWyZzFd5hv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AoqJu/dJMcaibOg6K/MhZCBEkHBI5CWyZzFd5hv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AoqJu/dJMcaibOg6K/MhZCBEkHBI5CWyZzFd5hv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAoqJu%2FdJMcaibOg6K%2FMhZCBEkHBI5CWyZzFd5hv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1274&quot; height=&quot;740&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Task들 간의 의존성은 다음과 같은 방식으로 정의할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC1awe/dJMb99TrvgK/Ea4At1yOT5Kxr0wlk018J1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC1awe/dJMb99TrvgK/Ea4At1yOT5Kxr0wlk018J1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC1awe/dJMb99TrvgK/Ea4At1yOT5Kxr0wlk018J1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC1awe%2FdJMb99TrvgK%2FEa4At1yOT5Kxr0wlk018J1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;508&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;조건부 실행&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 Task의 결과에 따라 다른 경로로 진행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qU4sG/dJMcagE4hJU/SnjVdLGnG1bz0AyqF3jGIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qU4sG/dJMcagE4hJU/SnjVdLGnG1bz0AyqF3jGIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qU4sG/dJMcagE4hJU/SnjVdLGnG1bz0AyqF3jGIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqU4sG%2FdJMcagE4hJU%2FSnjVdLGnG1bz0AyqF3jGIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;519&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;재시도 및 복구&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task 실패시 자동으로 재시도하거나 대체 작업을 실행할 수 있습니다. 이러한 의존성 관리 덕분에 복잡한 데이터 파이프라인도 안정적으로 운영할 수 있습니다. 예를 들어, 매일 아침 실행되는 데이터 품질 검사 Task가 실패했다고 가정해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Airflow는 설정된 재시도 정책에 따라 자동으로 Task를 다시 실행하며, 계속 실패할 경우 담당자에게 알림을 보내 즉각적인 대응이 가능하게 합니다. 문제 해결 후에는 실패한 지점부터 파이프라인을 재개할 수 있어, 전체 프로세스의 안정성을 보장합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVPcVZ/dJMcah41XyP/F9KdKh6rTXn03E1RyKlKyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVPcVZ/dJMcah41XyP/F9KdKh6rTXn03E1RyKlKyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVPcVZ/dJMcah41XyP/F9KdKh6rTXn03E1RyKlKyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVPcVZ%2FdJMcah41XyP%2FF9KdKh6rTXn03E1RyKlKyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 여러 데이터 소스로부터 정보를 수집하는 경우, 각각의 수집 Task를 병렬로 실행하여 전체 처리 시간을 단축할 수 있습니다. 만약 특정 소스에서 문제가 발생하더라도 다른 Task들은 정상적으로 진행되므로, 전체 파이프라인의 효율성을 유지할 수 있죠. 이처럼 Airflow는 파이썬 코드로 이러한 복잡한 워크플로우를 명확하게 정의하고 안정적으로 실행하며, 문제 발생 시 즉각적인 대응이 가능합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Task의 종류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task의 종류는 다음과 같습니다. 실무적으로는 Operator 사용이 거의 대부분이라 이부분만 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xJNRU/dJMcac3GrQF/1kGTFn0Nmr9oc2Ja1Pf1m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xJNRU/dJMcac3GrQF/1kGTFn0Nmr9oc2Ja1Pf1m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xJNRU/dJMcac3GrQF/1kGTFn0Nmr9oc2Ja1Pf1m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxJNRU%2FdJMcac3GrQF%2F1kGTFn0Nmr9oc2Ja1Pf1m0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;358&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Operator란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operator는 Task의 실제 작업을 정의하는 클래스입니다. &quot;실제로 무엇을 할지&quot;를 결정하는 실행 단위라고 생각하면 됩니다. 여기서 클래스란 사전에 어떻게 실행할 지 정해놓은 양식입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BashOperator : 명령어 실행 클래스&lt;/li&gt;
&lt;li&gt;PythonOperator : 파이썬 코드 실행 클래스&lt;/li&gt;
&lt;li&gt;PostgresOperator: DB 작업 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Operator를 조립하여 원하는 데이터 파이프라인을 만들 수 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;BashOperator&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BashOperator는 Shell 명령어를 실행하는 Operator입니다. 시스템 명령어 실행, 스크립트 실행, 파일 조작 등 Shell에서 할수 있는 모든 작업을 수행할 수 있습니다. 실무에서는 Python 스크립트를 별도의 모듈로 작성하고, 이를 BashOperator를 통해 실행하는 패턴을 많이 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;931&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yxeBj/dJMcadO1wo7/6l2nW7k9XMkL4TqDsypDo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yxeBj/dJMcadO1wo7/6l2nW7k9XMkL4TqDsypDo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yxeBj/dJMcadO1wo7/6l2nW7k9XMkL4TqDsypDo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyxeBj%2FdJMcadO1wo7%2F6l2nW7k9XMkL4TqDsypDo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;931&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;931&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 선호하는데는 두가지 이유가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 코드 관리의 용이성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 로직을 별도의 Python 파일로 분리하여 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;버전 관리 시스템(Git 등)을 통한 코드 리뷰가 수월해집니다.&lt;/li&gt;
&lt;li&gt;다른 DAG에서도 같은 스크립트를 재사용 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 디버깅의 편의성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python 스크립트를 독립적으로 테스트 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;IDE의 디버깅 도구를 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;로컬 환경에서 쉽게 테스트가 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;가상환경 관리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상환경 관리는 데이터 파이프라인 운영에서 매우 중요한 부분입니다. Airflow에서는 두 가지 방식으로 가상환경을 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. DAG 레벨에서의 관리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DAG 전체에서 사용할 가상환경을 지정합니다.&lt;/li&gt;
&lt;li&gt;모든 Task가 동일한 환경에서 실행됩니다.&lt;/li&gt;
&lt;li&gt;설정이 간단하고 일관성을 유지하기 쉽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Operator 레벨에서의 관리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Task마다 다른 가상환경을 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Task별로 필요한 라이브러리가 다를 때 유용합니다.&lt;/li&gt;
&lt;li&gt;더 세밀한 제어가 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 BashOperator 예시 코드에 대해 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;869&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfCLwQ/dJMcac3GrT4/sg9XixaPkFwXNtR1gbkQgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfCLwQ/dJMcac3GrT4/sg9XixaPkFwXNtR1gbkQgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfCLwQ/dJMcac3GrT4/sg9XixaPkFwXNtR1gbkQgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfCLwQ%2FdJMcac3GrT4%2Fsg9XixaPkFwXNtR1gbkQgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;869&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;869&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;264&quot; data-start=&quot;49&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 두 가지 방식의 BashOperator 사용을 보여줍니다. 첫 번째는 기본 실행 방식입니다.&lt;br /&gt;task_id를 'run_basic_script'라는 고유 식별자로 지정하고, bash_command를 통해 시스템의 기본 Python으로 스크립트를 실행합니다. 이 방식은 구현이 간단하지만, 시스템 Python에 의존하므로 라이브러리 간 충돌이 발생할 수 있다는 단점이 있죠.&lt;/p&gt;
&lt;p data-end=&quot;429&quot; data-start=&quot;266&quot; data-ke-size=&quot;size16&quot;&gt;두 번째는 가상환경을 사용하는 방식입니다. task_id를 'run_with_venv'로 지정하고, 세 가지 명령어를 연속적으로 실행합니다. 먼저 source 명령으로 특정 가상환경을 활성화하고, Python 스크립트를 실행한 후, deactivate 명령으로 가상환경을 비활성화하게 됩니다. 여러 줄의 명령어는 \ 문법으로 연결되며, 이 방식은 격리된 환경에서 실행되므로 의존성 충돌을 방지할 수 있다는 장점이 있습니다. 실무에서는 두 번째 방식을 더 많이 사용하는데, 가상환경을 통해 각 파이프라인에 필요한 정확한 라이브러리 버전을 관리할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-end=&quot;429&quot; data-start=&quot;266&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;429&quot; data-start=&quot;266&quot; data-ke-size=&quot;size20&quot;&gt;PythonOperator&lt;/h4&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;611&quot; data-ke-size=&quot;size16&quot;&gt;PythonOperator는 Python 함수를 실행하는 가장 유연한 Operator입니다.&lt;br /&gt;데이터 처리, API 호출, 복잡한 비즈니스 로직 등 Python으로 할 수 있는 모든 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;611&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;782&quot; data-start=&quot;731&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로는 Python 스크립트를 별도의 모듈로 만들어 두고, import해서 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TDIVn/dJMcaco6VO7/iUlBJ41und8q1mqEWFnFKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TDIVn/dJMcaco6VO7/iUlBJ41und8q1mqEWFnFKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TDIVn/dJMcaco6VO7/iUlBJ41und8q1mqEWFnFKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTDIVn%2FdJMcaco6VO7%2FiUlBJ41und8q1mqEWFnFKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1240&quot; height=&quot;542&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시처럼 이렇게 코드를 구성하면 유지보수가 쉽고, 재사용성도 높아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;266&quot; data-end=&quot;429&quot;&gt;PostgresOperator&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 PostgreSQL 데이터베이스 작업을 위한 Operator입니다. 이러한 데이터베이스를 작업할 때는 크게 두가지 접근 방식이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. PythonOperator 내에서 직접 데이터베이스 연결 코드를 작성하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Airflow에서 제공하는 데이터베이스 전용 Operator를 사용하는 방법&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Faq78/dJMcaipkC4G/t2sYnuknwelDuAyFSe5fC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Faq78/dJMcaipkC4G/t2sYnuknwelDuAyFSe5fC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Faq78/dJMcaipkC4G/t2sYnuknwelDuAyFSe5fC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFaq78%2FdJMcaipkC4G%2Ft2sYnuknwelDuAyFSe5fC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;632&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by1lbZ/dJMcahqqtsL/uoPKFRBgmXTlOWP4RT39rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by1lbZ/dJMcahqqtsL/uoPKFRBgmXTlOWP4RT39rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by1lbZ/dJMcahqqtsL/uoPKFRBgmXTlOWP4RT39rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby1lbZ%2FdJMcahqqtsL%2FuoPKFRBgmXTlOWP4RT39rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1206&quot; height=&quot;664&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgresOperator를 사용하면 연결 관리나, 에러 처리와 같은 기본적인 기능을 Airflow가 알아서 처리해 주기 때문에 코드가 더 간결해지고 관리하기 쉬워집니다. 특히 데이터베이스 연결 정보를 Airflow의 Connection 시스템을 통해 중앙에서 관리할 수 있다는 것이 큰 장점입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhYdPu/dJMcaaLBawM/sNSvs8bGBVKhu6rWOwpk7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhYdPu/dJMcaaLBawM/sNSvs8bGBVKhu6rWOwpk7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhYdPu/dJMcaaLBawM/sNSvs8bGBVKhu6rWOwpk7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhYdPu%2FdJMcaaLBawM%2FsNSvs8bGBVKhu6rWOwpk7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;432&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 Airflow는 다양한 Operator를 제공합니다. 이렇게 특화된 Operator는 각각의 서비스나 플랫폼과의 연결 이동을 쉽게 만들어줍니다. 그러나 꼭 전용 Operator를 사용할 필요는 없습니다. Python 스크립트로 직접 해당 기능을 구현하고, 이를 PythonOperator나 BashOperator로 실행하는 것도 좋은 방법이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로는 최근 'Cursor'와 같은 AI 기반 IDE 발전 덕분에, PythonOperator, BashOperator, 그리고 DB관련 Operator 정도만 사용하고, 나머지 기능들은 직접 구현하는 것을 선호합니다. 물론 접근 방식은 회사마다 다를 수 있습니다. 유지보수와 관리 측면에서 기본 Operator 사용을 선호하는 조직도 있습니다. 그러나 주석을 잘 달고 문서화를 철저히 한다면, 직접 구현하는 방식도 충분히 좋은 선택이 될 수 있다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Airflow 전체 구성요소&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 설명한 DAG와 Task 외에도 Airflow는 다양한 구성 요소로 이루어져 있습니다. 마치 정교한 시계처럼 여러 부품이 조화롭게 작동하는 시스템이죠. 전체 구성요소를 좀 더 쉽게 알아보기 위해&lt;b&gt; 공항과 활주로에&lt;/b&gt; 비유해 봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDB3DY/dJMcafznFDc/yk5eo3ROy9ytX1sjuLvDGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDB3DY/dJMcafznFDc/yk5eo3ROy9ytX1sjuLvDGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDB3DY/dJMcafznFDc/yk5eo3ROy9ytX1sjuLvDGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDB3DY%2FdJMcafznFDc%2Fyk5eo3ROy9ytX1sjuLvDGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;743&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위 구성 요소들의 워크플로우를 시각화 하면 다음과 같습니다. Airflow는 인천공항과 같은 곳으로, 각 구성 요소가 맡은 역할이 있고, 이들이 서로 협력하면서 안정적인 쿼프를로우 실행을 가능하게 만들어 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJBhW0/dJMcaaSnHMM/6CBBIeuWRmWU2HN4vhUXM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJBhW0/dJMcaaSnHMM/6CBBIeuWRmWU2HN4vhUXM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJBhW0/dJMcaaSnHMM/6CBBIeuWRmWU2HN4vhUXM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJBhW0%2FdJMcaaSnHMM%2F6CBBIeuWRmWU2HN4vhUXM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;1270&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Web Server(웹 서버): 공항 전체&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;270&quot; data-start=&quot;65&quot; data-ke-size=&quot;size16&quot;&gt;웹 서버는 인천공항과 같은 공항 전체라고 생각하면 됩니다. 하나의 거대한 시스템으로서, 모든 운영을 총괄하고 감독하는 역할을 하죠. 공항이 터미널, 활주로, 관제탑을 통합 관리하듯이, 웹 서버도 여러 스케줄러, Executor, Worker를 하나의 시스템으로 통합해서 관리합니다. 모든 운영 현황은 대시보드(공항의 중앙 관제 시스템)를 통해 한눈에 파악할 수 있죠.&lt;/p&gt;
&lt;p data-end=&quot;270&quot; data-start=&quot;65&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;270&quot; data-start=&quot;65&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Scheduler(스케줄러): 터미널&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;304&quot; data-ke-size=&quot;size16&quot;&gt;스케줄러는 공항의 터미널과 같습니다. 인천공항의 T1, T2처럼 여러 개의 터미널이 동시에 운영될 수 있죠. 각 터미널은 독립적으로 운영되면서도 전체 공항 시스템과 긴밀하게 연결됩니다. 각 터미널이 자신의 게이트와 비행기들을 관리하듯이, 각 스케줄러도 자신에게 할당된 DAG을 독립적으로 관리합니다. 한 터미널에 문제가 생겨도 다른 터미널은 정상 운영될 수 있는 것처럼, 여러 스케줄러를 운영하면 시스템의 안정성과 확장성을 높일 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;304&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;304&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Worker(작업자): 비행기&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;304&quot; data-ke-size=&quot;size16&quot;&gt;Worker는 실제로 하늘을 나는 비행기입니다. 활주로를 통해 이륙한 비행기들이 각자의 목적지를 향해 날아가듯이, Worker는 각자 할당받은 Task를 수행합니다. 각 비행기가 독립적으로 운항하면서도 관제 시스템과 지속적으로 통신하듯이, Worker도 독립적으로 작업을 수행하면서 중앙 시스템과 상태를 공유하죠.&lt;/p&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;304&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;304&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Executor(실행기): 활주로&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;304&quot; data-ke-size=&quot;size16&quot;&gt;Executor는 터미널에 속한 활주로입니다. 각 터미널마다 여러 개의 활주로를 가질 수 있고, 이 활주로를 통해 실제 비행기(Worker)들이 이륙(Task)하게 됩니다. 공항마다 활주로 운영 방식이 다르듯이, Airflow도 상황에 맞는 다양한 Executor를 선택할 수 있습니다. 가장 기본이 되는 SequentialExecutor는 작은 지방 공항처럼 활주로가 하나밖에 없습니다. 비행기가 아무리 많아도 한 번에 한 대씩만 이륙할 수 있죠. 개발 환경이나 테스트용으로는 충분하지만, 실제 운영 환경에서는 비효율적일 수도 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;551&quot; data-start=&quot;304&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0qYYZ/dJMcaiJAaXJ/kvUvuCkbqJkP500XDl8a9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0qYYZ/dJMcaiJAaXJ/kvUvuCkbqJkP500XDl8a9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0qYYZ/dJMcaiJAaXJ/kvUvuCkbqJkP500XDl8a9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0qYYZ%2FdJMcaiJAaXJ%2FkvUvuCkbqJkP500XDl8a9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1136&quot; height=&quot;170&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, CeleryExcutor는 인천공항처럼 대형 국제공항입니다. 여러 터미널에 많은 활주로가 있어서 수많은 비행기를 동시에 처리할 수 있습니다. 다른 지역의 공항과도 긴밀하게 협력하면서 운영됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JxRfb/dJMcabX2yDp/eKbvNv9c2MPN3mKKfXb2O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JxRfb/dJMcabX2yDp/eKbvNv9c2MPN3mKKfXb2O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JxRfb/dJMcabX2yDp/eKbvNv9c2MPN3mKKfXb2O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJxRfb%2FdJMcabX2yDp%2FeKbvNv9c2MPN3mKKfXb2O1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;256&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KubernetesExecutor는 미래의 스마트 공항이라고 할 수 있는데요. 필요할때마다 활주로를 자동으로 만들고, 사용이 끝나면 다른 용도로 전환할 수 있습니다. 각 비행기는 완벽하게 독립된 공간에서 운영되며, 자원도 효율적으로 관리됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w1R6H/dJMcafF8Ugn/FG6PTSs9QpEMO12s8iuNPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w1R6H/dJMcafF8Ugn/FG6PTSs9QpEMO12s8iuNPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w1R6H/dJMcafF8Ugn/FG6PTSs9QpEMO12s8iuNPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw1R6H%2FdJMcafF8Ugn%2FFG6PTSs9QpEMO12s8iuNPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;892&quot; height=&quot;584&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 어떤 Executor를 선택할지는 여러분의 공항 규모와 운영 방식에 달려있습니다. 하루에 몇 개의 비행기(Task)를 운영해야 하나요? 얼마나 많은 승객(데이터)을 처리해야 하나요? 미래에는 얼마나 확장할 계획인가요? 지금보다 규모가 더 커질 것으로 예상한다면, 처음부터 CeleryExecutor나 KubernetesExecutor를 고려하는 것이 현명할 수 있습니다. 물론 더 복잡한 관제 시스템이 필요하지만, 장기적으로는 더 안정적인 운영이 가능할 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; XCom(크로스 커뮤니케이션): 항공 통신 시스템&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XCom은 공항의 항공 통신 시스템과 같습니다. 비행기들(Workers)이 서로 중요한 정보를 주고받을 때 사용하는 무전기나 데이터링크 시스템이라고 생각하면 됩니다. 예를 들어, A 비행기가 특정 구역의 기상 정보를 발견했다면, 이를 데이터로 B 비행기에 전달할 수 있죠. 다만, 대규모 데이터를 효율적으로 전송할 수 없어서, XCom은 대용량 데이터 전송보다는 간단한 상태 정보나 제어 신호를 주고받는 데 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Metadata Database(메타데이터 데이터베이스): 항공 운항 기록 시스템&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;807&quot; data-start=&quot;630&quot; data-ke-size=&quot;size16&quot;&gt;메타데이터 데이터베이스는 공항의 운항 기록 시스템입니다. 모든 비행기의 이착륙 시간, 운항 경로, 특이 사항 등을 꼼꼼히 기록하는 블랙박스와 같은 역할을 합니다. 마치 항공사가 과거 운항 기록을 분석하여 더 효율적인 스케줄을 짜고 안전성을 높이듯이, 이 기록들은 파이프라인 최적화와 문제 해결에 귀중한 자료가 됩니다.&lt;/p&gt;
&lt;p data-end=&quot;935&quot; data-start=&quot;809&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 특정 노선(Task)에서 지연이 자주 발생한다면, 그 원인을 파악하고 개선할 수 있죠. 중요한 점은 운영 환경에서는 반드시 PostgreSQL이나 MySQL과 같은 프로덕션 데이터베이스를 사용해야 한다는 것입니다.&lt;/p&gt;
&lt;p data-end=&quot;1181&quot; data-start=&quot;937&quot; data-ke-size=&quot;size16&quot;&gt;기본으로 설정되는 SQLite는 개발 환경에서만 사용해야 합니다. SQLite는 동시 접근을 제대로 처리하지 못하기 때문에, 여러 스케줄러나 워커가 동시에 접근하면 데이터베이스 잠금 문제가 발생할 수 있습니다. 특히 CeleryExecutor, KubernetesExecutor를 사용할 때는 PostgreSQL 또는 MySQL 사용이 필수적입니다. 이러한 분산 환경에서는 안정적인 동시성 제어와 트랜잭션 관리가 매우 중요하기 때문이죠.&lt;/p&gt;
&lt;p data-end=&quot;1181&quot; data-start=&quot;937&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1181&quot; data-start=&quot;937&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1181&quot; data-start=&quot;937&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 Apache Airflow의 기본 개념부터, DAG, Task라는 핵심 구성요소, 그리고 다양한 Operator의 활용법까지 알아보았습니다. Airflow는 단순한 스케줄러 이상의 강력한 워크플로우 관리 도구로, 현대 데이터 엔지니어링의 핵심 축을 담당하고 있습니다. 특히 DAG를 통해 복잡한 데이터 파이프라인을 명확하고, 유지 보수하기 쉬운 형태로 정의할수 있다는 점이 가장 큰 장점이라 할수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;1181&quot; data-start=&quot;937&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1181&quot; data-start=&quot;937&quot; data-ke-size=&quot;size16&quot;&gt;실제 운영 환경에서는 단순히 기능을 아는 것을 넘어, 시스템 아키텍처에 대한 이해가 매우 중요합니다. Web Server, Scheduler, Worker, Executor등 각 구성 요소들이 어떻게 협력하여 작동하는지 이해함으로써, 더 안정적이고 효율적인 파이프라인을 구축할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;1181&quot; data-start=&quot;937&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1181&quot; data-start=&quot;937&quot; data-ke-size=&quot;size16&quot;&gt;2025년에 3.0 버전을 출시를 계획중인 Airflow는 계속해서 발전하고 있는 도구입니다. 꾸준히 새로운 기능을 선보이고 있고, 커뮤니티도 활발하게 운영중입니다. 앞으로 데이터 파이프라인의 복잡성은 더 증가할것으로 보여, Airflow 같은 워크플로우 관리 도구의 중요성도 커질것 같습니다. 이번글을 통해 Airflow를 이해할 수 있는 기회가 되길 바랍니다.&lt;/p&gt;</description>
      <category>Airflow</category>
      <category>airflow</category>
      <category>Dae</category>
      <category>dag</category>
      <category>Orchestraion</category>
      <category>데이터베이스</category>
      <category>데이터엔지니어</category>
      <category>메타데이터</category>
      <category>에어플로우</category>
      <category>오케스트레이션</category>
      <author>jyu_seo_</author>
      <guid isPermaLink="true">https://jyu-seo.tistory.com/185</guid>
      <comments>https://jyu-seo.tistory.com/185#entry185comment</comments>
      <pubDate>Sun, 5 Apr 2026 13:25:55 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크세미나] - MLOps를 활용한 AI 서비스 개발 스토리 세미나 후기</title>
      <link>https://jyu-seo.tistory.com/184</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 우아한형제들에서 테크세미나의 주제를 AI와 MLOps로 정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온라인으로도 열심히 들었고 내용을 기억해보고자 정리해보려한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=MA5A7Xqb-7U&quot;&gt;youtube.com/watch?v=MA5A7Xqb-7U&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=MA5A7Xqb-7U&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bG145q/dJMb9kT3xnM/DxK1kFLwTR6JW8xMBc8IR1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bfxfjL/dJMb9cBIFwB/kcIbGh5g1zW1A0I5LIW1RK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[우아한테크세미나] MLOps를 활용한 AI 서비스 개발 스토리&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/MA5A7Xqb-7U&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 우아한 형제들의 AI 서비스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 리뷰검수: 해당 이미지가 올바른 이미지인지 검수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 배달 예상시간 예측&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 추천알고리즘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이상거래탐지, 서비스 이상탐지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 메뉴명 분류&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 메뉴명 어뷰징&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. AI 서비스를 적용할 때의 어려움&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 - 개발환경에서 운영환경으로 배포가 어려움:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ML시스템의 복잡함과 다양한 개발환경에서 각각 다른 방식으로 개발하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. MLOps를 도입하여 모델 개발, 서빙, 배포를 효율적으로 하게 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이를위해 MLOps Cycle과 AI Studio를 만듦&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 모델개발에서 운영배포까지 단계 줄이기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 새로운기능과 서비스 개발 확장이 가능하게 하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. 어려움을 해결하기 위한 MLOps 구축&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ML Project, Pipline Builder, ML SDK로 구성하여 MLOps를 구축&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3-1 ML Project&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ML Project에서 AI를 개발하고 개발한 소스들은 container 환경에서 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ML Project는 코드저장소이고, 여러 프고젝트를 하나의 레퍼지토리에서 관리하는 Monorepo형식으로 구축&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-04-05 113339.png&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB5J3K/dJMcacilp7P/DQaiWQGF7UWmWftXdBogYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB5J3K/dJMcacilp7P/DQaiWQGF7UWmWftXdBogYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB5J3K/dJMcacilp7P/DQaiWQGF7UWmWftXdBogYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB5J3K%2FdJMcacilp7P%2FDQaiWQGF7UWmWftXdBogYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;717&quot; height=&quot;252&quot; data-filename=&quot;화면 캡처 2026-04-05 113339.png&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습/서빙/데모 코드를 Monorepo로 관리하는 이유 : 코드를 재사용하기 위함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 모든 코드들은 쿠버네티스를 통해 container에서 실행되고 있으며 개발에 사용했던 환경 그대로 운영에 배포할 수 있었고 작업 소요 시간을 단축함.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3-2 Pipeline Builder&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yaml파일로 정의를하고 Pipeline Builder를 통해서 airflow dag를 만드는 과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DagFactory Kustomize 기반으로 설계함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통이 되는 base.yaml을 작성하고 각 기능별로 yaml을 작성하여 연결하는 방법으로 사용하기 때문에 유연성과 확장성이 좋음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;operator를 조정하여 cpu, gpu를 쉽게 바꿔가면서 사용할 수 있음.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3-3 ML SDF&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 서비스 개발을 위한 기능을 제공하는 파이썬 라이브러리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점 : ML Pipeline을 구성하는 과정에서 모델생성을 제외한 데이터 다운로드, 읽기, 실험결과 저장, 예측결과 내보내기등을 제공하여 모델 개발자는 모델 개발에만 신경 쓸 수 있도록 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4. MLOps Level&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한형제들에서는 MLOps Level을 아래와 같이 정의했고, 본인들이 만들고 위에서 설명한 AI플랫폼은 현재 1.4~1.6레벨 사이에 있다고 함. 레벨2에 도달해 누구든지 AI서비스를 만들수 있는 플랫폼이 되도록 성장하는 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-04-05 113815.png&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MCXwu/dJMcaiixZy0/aN3fu1L3JPlgjAxdlDl7TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MCXwu/dJMcaiixZy0/aN3fu1L3JPlgjAxdlDl7TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MCXwu/dJMcaiixZy0/aN3fu1L3JPlgjAxdlDl7TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMCXwu%2FdJMcaiixZy0%2FaN3fu1L3JPlgjAxdlDl7TK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;307&quot; data-filename=&quot;화면 캡처 2026-04-05 113815.png&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;5. 앞으로 발전해야 하는 부분과 AI플랫폼 적용 사례&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 LLMOps,Vector DB등의 부분에서 발전을 이뤄야함.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;5-1. 사례 : 알뜰배달 시간 예측 서비스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터: 요일,시간,날짜,거리,라이더 상태 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델: 딥러닝,실시간&amp;amp;준실시간 서빙 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. ML Project에 전처리,학습,추론,내보내기 등의 코드 개발&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ML SDK의 kafka,redis 클라이언트로 실시간 피쳐 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.실험 설정 - mlflow 연동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.테스트 - docker기반, 운영환경과 동일하게 테스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.Pipeline Builder를 이용한 파이프라인 개발 - Airflow DAG 개발 -&amp;gt; Spark To S3 -&amp;gt; Tarin -&amp;gt; UploadModel -&amp;gt; Inference&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Airflow 테스트 및 배포&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 서빙/ 운영&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 추론 결과분석 &amp;amp; 피드백&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>책&amp;amp;스터디</category>
      <category>airflow</category>
      <category>ML</category>
      <category>데이터엔지니어</category>
      <category>머신러닝</category>
      <category>에어플로우</category>
      <category>우아한형제들</category>
      <author>jyu_seo_</author>
      <guid isPermaLink="true">https://jyu-seo.tistory.com/184</guid>
      <comments>https://jyu-seo.tistory.com/184#entry184comment</comments>
      <pubDate>Sun, 5 Apr 2026 11:41:56 +0900</pubDate>
    </item>
    <item>
      <title>[Flink] - Apache Flink</title>
      <link>https://jyu-seo.tistory.com/183</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번블로그에서는 Flink에 대한 기본 개념과 내부구조, 장단점에 대해서 정리 해보도록 하겠습니다.&lt;/p&gt;
&lt;h2 id=&quot;ams#what-isc1#pattern-data&quot; style=&quot;color: #232f3e; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Apache Flink&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;[데이터 스트림에 대한 상태 기반 연산]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apache Flink는 제한되지 않은(스트림) 데이터 세트와 제한된(배치) 데이터 세트에 대한 상태 저장 처리를 위한 오픈 소스 분산 엔진입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Apache Flink는 유한 및 무한 데이터 스트림에 대한 상태 기반 연산을 처리하기 위한 프레임워크이자 분산 처리 엔진&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Flink는 모든 일반적인 클러스터 환경에서 실행되도록 설계되었으며, 인메모리 속도로 어떤 규모에서도 연산을 수행할 수 있도록 만들어짐 &amp;rarr;&amp;nbsp;&lt;b&gt;YARN, Kubernetes(EKS 포함), Mesos, EMR, Standalone&lt;/b&gt;&amp;nbsp;등의 다양한 클러스터 환경에서&amp;nbsp;&lt;b&gt;무리 없이 실행 가능&lt;/b&gt;하도록 설계됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;ams#what-isc2#pattern-data&quot; style=&quot;background-color: #fbfbfb; color: #232f3e; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Apache Flink를 사용하는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fbfbfb; color: #333333; text-align: start;&quot;&gt;Apache Flink는 다양한 기능으로 인해 다양한 유형의 스트리밍 및 배치 애플리케이션을 구축하는 데 사용됩니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fbfbfb; color: #333333; text-align: start;&quot;&gt;Apache Flink로 구동되는 일반적인 유형의 애플리케이션은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fbfbfb; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이벤트 기반 애플리케이션: &lt;/b&gt;하나 이상의 이벤트 스트림에서 이벤트를 수집하고 계산, 상태 업데이트 또는 외부 작업을 실행합니다. 상태 저장 처리를 통해 결과가 수집된 이벤트 기록에 따라 달라지는 단일 메시지 변환 이상의 논리를 구현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 분석 애플리케이션: &lt;/b&gt;데이터에서 정보와 인사이트를 추출합니다. 기존에는 유한한 데이터 세트를 쿼리하고 쿼리를 다시 실행하거나 결과를 수정하여 새 데이터를 통합하는 방식으로 실행되었습니다. Apache Flink를 사용하면 지속적인 업데이트, 쿼리 스트리밍 또는 수집된 이벤트를 실시간으로 처리하고 결과를 지속적으로 내보내고 업데이트하여 분석을 실행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 파이프라인 애플리케이션:&lt;/b&gt;&amp;nbsp;한 데이터 스토리지에서 다른 데이터 스토리지로 이동할 데이터를 변환하고 강화합니다. 전통적으로 추출-변환-적재(ETL)는 주기적으로 배치로 실행됩니다. Apache Flink를 사용하면 프로세스가 지속적으로 작동하여 짧은 대기 시간으로 데이터를 대상으로 이동할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;ams#what-isc2#pattern-data&quot; style=&quot;background-color: #fbfbfb; color: #232f3e; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Apache Flink 특징 및 장점&lt;/h2&gt;
&lt;h4 id=&quot;process-unbounded-and-bounded-data&quot; style=&quot;background-color: #ffffff; color: #232f3e; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1) Process Unbounded and Bounded Data(유한 무한 데이터 처리)&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: left;&quot;&gt;고급 분석용 API에서 상세한 제어 기능을 제공하는 상태 저장 이벤트 기반 애플리케이션 수준에 이르기까지 *&lt;/span&gt;&lt;b&gt;계층화된 API를 제공&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle; background-color: #ffffff; color: #222832; text-align: left;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;b&gt;SQL / Table API:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;Flink 애플리케이션 작성 시 Bounded 및 Unbounded Streams 모두에서 사용 가능한 선언적 고수준 API&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;b&gt;DataStream API:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Flink 애플리케이션 작성 시 UnBounded Streams 에서 사용되는 고수준 API&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;b&gt;DataSet API:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Flink 애플리케이션 작성시 Bounded Streams에서 사용되는 더 낮은 수준의 API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l5m3o/dJMcafMQf6D/OoZDucwUIoAXcodvdpEhGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l5m3o/dJMcafMQf6D/OoZDucwUIoAXcodvdpEhGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l5m3o/dJMcafMQf6D/OoZDucwUIoAXcodvdpEhGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl5m3o%2FdJMcafMQf6D%2FOoZDucwUIoAXcodvdpEhGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;844&quot; height=&quot;143&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;무한 스트림 (Unbounded Streams)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-end=&quot;513&quot; data-start=&quot;280&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;313&quot; data-start=&quot;280&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;시작점은 있지만 끝이 정의되지 않은 스트림&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;343&quot; data-start=&quot;314&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지속적으로 데이터가 생성되며, 종료되지 않음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;431&quot; data-start=&quot;344&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터를&amp;nbsp;&lt;b&gt;계속해서 실시간으로 처리&lt;/b&gt;해야 하며, 데이터를 모두 수집한 후 처리하는 것은 불가능(왜냐하면&amp;nbsp;&lt;b&gt;끝이 없기 때문&lt;/b&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;513&quot; data-start=&quot;432&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서&amp;nbsp;&lt;b&gt;이벤트가 발생한 순서대로 처리&lt;/b&gt;하는 것이 중요할 수 있음, 그래야 결과의 정확성과 완전성을 판단할 수 있기 때문&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;유한 스트림 (Bounded Streams)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-end=&quot;736&quot; data-start=&quot;553&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;582&quot; data-start=&quot;553&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;시작과 끝이 명확하게 정의된 스트림&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;622&quot; data-start=&quot;583&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 데이터를&amp;nbsp;&lt;b&gt;먼저 수집한 후&lt;/b&gt;, 이후에 처리할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;677&quot; data-start=&quot;623&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이벤트의 순서가 중요하지 않음&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유한한 데이터셋은 항상 정렬할 수 있기 때문&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;736&quot; data-start=&quot;678&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 유한 스트림 처리 방식은 흔히&amp;nbsp;&lt;b&gt;배치 처리(Batch Processing)&lt;/b&gt;&amp;nbsp;라고도 함&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;736&quot; data-start=&quot;678&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Flink는 &quot;스트리밍이 기본, 배치는 특수 케이스&quot;&lt;/b&gt;&amp;nbsp;라는 철학을 가지고 설계되었음&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-end=&quot;283&quot; data-start=&quot;209&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot; data-end=&quot;248&quot; data-start=&quot;209&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;스트리밍을 기본 단위로 처리&lt;/b&gt;&amp;nbsp;&amp;rarr; 실시간 데이터 처리에 최적화&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot; data-end=&quot;283&quot; data-start=&quot;249&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배치도 내부적으로는&amp;nbsp;&lt;b&gt;유한한 스트림&lt;/b&gt;으로 간주하여 처리&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot; data-end=&quot;283&quot; data-start=&quot;249&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spark 같은 시스템은 기본적으로 배치(batch) 기반이고 스트리밍은 그 위에 올라간 구조라서,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실시간 처리의 지연 시간이나 처리 방식에서 Flink보다 불리한 경우가 많음.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;스트리밍 시스템은 윈도우를 통해 무한한 스트림 데이터를 유한개의 데이터로 바꿔서 처리한다는 아이디어&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lpkrN/dJMcafsuU8b/qFkOGvMjCq36pigedU476K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lpkrN/dJMcafsuU8b/qFkOGvMjCq36pigedU476K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lpkrN/dJMcafsuU8b/qFkOGvMjCq36pigedU476K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlpkrN%2FdJMcafsuU8b%2FqFkOGvMjCq36pigedU476K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;303&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2) Deploy Applications Anywhere(어디서나 앱을 배포)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Hadoop YARN, Kubernetes 등 일반적으로 사용되는 모든 클러스터 리소스 관리자와 통합되며, 단독(standalone) 클러스터로 설정하여 실행할 수 있음&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Flink는&amp;nbsp;&lt;b&gt;각 리소스 관리자에 맞는 배포 모드&lt;/b&gt;를 제공하여,리소스 관리자와&amp;nbsp;&lt;b&gt;그에 맞는 방식(idiomatic way)&lt;/b&gt;&amp;nbsp;으로 상호작용할 수 있도록 함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;병렬성(parallelism) 을 기반으로 필요한 리소스를 자동으로 파악하고, 해당 리소스를 리소스 관리자에게 요청&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;애플리케이션을 제출하거나 제어하는 모든 통신은&amp;nbsp;&lt;b&gt;REST 호출(REST API)&lt;/b&gt;&amp;nbsp;을 통해 이루어지므로,&amp;nbsp;Flink는&amp;nbsp;&lt;b&gt;다양한 환경에 쉽게 통합&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3) Run Applications at any Scale(다양한 규모의 작업으로 실행)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;208&quot; data-start=&quot;0&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;애플리케이션은 수천 개의 태스크(task) 로 병렬화되며, 이 태스크들은 클러스터 내에서 분산되어 동시에 실행&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot; data-end=&quot;208&quot; data-start=&quot;0&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;애플리케이션은 사실상 무제한에 가까운 CPU, 메인 메모리, 디스크, 네트워크 IO 자원을 활용할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;436&quot; data-start=&quot;210&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Flink는 매우 큰 규모의 애플리케이션 상태(state) 도 쉽게 관리할 수 있음 &amp;rarr;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Flink의 비동기적(asynchronous)이고 점진적인(incremental) 체크포인팅 알고리즘은 처리 지연(latency)에 거의 영향을 주지 않으면서, 정확히 한 번만 처리되는 상태 일관성(exactly-once state consistency) 을 보장&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4) Leverage In-Memory Performance(인메모리 성능 활용)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상태 기반 Flink 애플리케이션은&amp;nbsp;&lt;b&gt;로컬 상태(local state)에 대한 접근을 최적화&lt;/b&gt;하도록 설계되어 있음&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;태스크의 상태는 항상&amp;nbsp;&lt;b&gt;메모리 내에 유지&lt;/b&gt;되며, 만약 상태 크기가 가용 메모리를 초과할 경우에는&amp;nbsp;&lt;b&gt;접근 효율이 높은 디스크 기반 자료 구조&lt;/b&gt;에 저장&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;기본 비즈니스 로직을 실행하는 모든 애플리케이션은 이벤트 또는 중간 결과를 기억하여 나중에 다음 이벤트가 수신되거나 특정 기간이 지난 후에 접근할 수 있도록 함)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;Java 애플리케이션으로 JVM(Java Virtual Machine)에서 실행되지만 JVM GC(Garbage Collector)에 전적으로 의존하지 않고, 대신 커스텀 메모리 관리자를 구현하여 안정적인 메모리 사용량을 유지하면서 성능을 향상시킴&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Flink는 장애가 발생했을 경우에도&amp;nbsp;&lt;b&gt;정확히 한 번만 처리되는 상태 일관성(exactly-once state consistency)&lt;/b&gt;&amp;nbsp;을 보장하기 위해,&amp;nbsp;&lt;b&gt;로컬 상태를 주기적으로 비동기 방식으로 내구성 있는 저장소에 체크포인팅 함&lt;br /&gt;&lt;/b&gt;(경량 분산 스냅샷을 구현&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;해서 Exactly-once 를 보장하면서 오버헤드도 낮출 수 있음)&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;Kafka Streams 같은 경량 스트리밍 라이브러리는 상태 저장이 제한적이고 장애 복구가 어렵고,&lt;br /&gt;Spark Structured Streaming은 latency나 상태 복구에서 Flink보다 느림&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-end=&quot;270&quot; data-start=&quot;181&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 태스크는 대부분의 연산을&amp;nbsp;&lt;b&gt;로컬 상태(주로 메모리)에 접근하여 수행&lt;/b&gt;하므로,&amp;nbsp;&lt;b&gt;아주 낮은 처리 지연(latency)&lt;/b&gt;&amp;nbsp;을 달성할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNLbc3/dJMcadnTgpF/85LHLeaeTsOxF6vgJGKkfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNLbc3/dJMcadnTgpF/85LHLeaeTsOxF6vgJGKkfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNLbc3/dJMcadnTgpF/85LHLeaeTsOxF6vgJGKkfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNLbc3%2FdJMcadnTgpF%2F85LHLeaeTsOxF6vgJGKkfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;289&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BCkjo/dJMcacbtYed/4Z5bHnqK8dsEnBFJra2h3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BCkjo/dJMcacbtYed/4Z5bHnqK8dsEnBFJra2h3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BCkjo/dJMcacbtYed/4Z5bHnqK8dsEnBFJra2h3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBCkjo%2FdJMcacbtYed%2F4Z5bHnqK8dsEnBFJra2h3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;420&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;5) 데이터 처리 방식, Event Time 처리와 정확한 시간 제어&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;lazy evaluation:&lt;/b&gt;&amp;nbsp;계산을 최대한 늦출 수 있는 lazy evaluation을 사용. 즉, 계산이 필요할 때까지 계산이 수행되지 않음(실제 필요할 때 메모리에 올림)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;지연 데이터 처리:&lt;/b&gt;&amp;nbsp;이벤트 발생 시간과 처리 시간을 구분하고 워터마크를 사용하여 지연 데이터를 처리 함. 즉, 데이터 포인트는 들어오는 즉시 처리되지 않더라도 처리될 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Event Time 기반 윈도우를 사용하여 데이&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;터가 실제로&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;언제 발생했는지(event time)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;를 기준으로 처리 가능&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실시간 IoT, 결제, 사용자 행동 분석 같이&amp;nbsp;&lt;b&gt;시간 순서가 중요하거나 늦게 오는 이벤트가 많은 상황&lt;/b&gt;에서&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Flink는 매우 정교한 처리가 가능하지만, Spark는 처리 순서나 지연 데이터 핸들링이 제한적임&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;out-of-order도 watermark(&quot;이제 특정 시간 이전의 데이터는 더 이상 도착하지 않을 것이다&quot;라는 신호 )+ event time을 기준으로 데이터 처리 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;스트림 데이터란&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;계속해서 끊임없이 생성되고 흐르는 데이터를 의미&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한 번에 한 건씩(또는 작은 단위로) 발생하며, 실시간으로 처리되는 것이 일반적&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;스트림 데이터 예시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;9471&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;쇼핑몰 고객의 구매 요청&lt;/li&gt;
&lt;li id=&quot;737c&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;항공사 예약 발생&lt;/li&gt;
&lt;li id=&quot;008c&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;보험금 청구&lt;/li&gt;
&lt;li id=&quot;1895&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;은행 트랜잭션 발생&lt;/li&gt;
&lt;li id=&quot;b065&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;클릭 이벤트&lt;/li&gt;
&lt;li id=&quot;b1c6&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;서버 로그&lt;/li&gt;
&lt;li id=&quot;9470&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;현재 IoT 장비의 위치&lt;/li&gt;
&lt;li id=&quot;913d&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;기타 등등&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;스트림 데이터를 활용하는 예시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;0558&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;은행에서 이상 거래를 탐지&lt;/li&gt;
&lt;li id=&quot;0164&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;쇼핑몰에서 구매자의 수요에 맞춰 동적 가격 계산&lt;/li&gt;
&lt;li id=&quot;2288&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;온라인 사용자의 행동 패턴 분석&lt;/li&gt;
&lt;li id=&quot;4b54&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;실시간 추천 시스템&lt;/li&gt;
&lt;li id=&quot;4e47&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;기타 등등&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;streaming 데이터 시점 기준&lt;/h4&gt;
&lt;table style=&quot;color: #666666; text-align: center; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;기준&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Processing Time&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;데이터를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;처리하는 시점&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기준&lt;/td&gt;
&lt;td&gt;구현 간단, 지연 고려 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Ingestion Time&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;시스템에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;들어온 시점&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기준&lt;/td&gt;
&lt;td&gt;중간 타협, 약간의 시간 보존 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Event Time&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;데이터가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;실제로 발생한 시각&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기준&lt;/td&gt;
&lt;td&gt;가장 정확, 워터마크 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SparkStreaming의 처리 시점은?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;1379&quot; data-end=&quot;1423&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;기본값: Processing Time 기반&lt;/b&gt;&amp;nbsp;(마이크로배치 시점 기준)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;1424&quot; data-end=&quot;1539&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;옵션으로 Event Time 처리 가능&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;1455&quot; data-end=&quot;1539&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;1455&quot; data-end=&quot;1505&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;withWatermark()를 사용해 event time + 늦은 데이터 처리 지원&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;1508&quot; data-end=&quot;1539&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만&amp;nbsp;&lt;b&gt;Flink만큼 정교하거나 저지연은 아님&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;1540&quot; data-end=&quot;1595&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Ingestion Time&lt;/b&gt;은 명시적으로 지원하지 않음 (사용자가 처리 시점 기록해야 함)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774768383406&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Out-of-order 데이터
이벤트가 실제로 발생한 시간과는 다른 순서로 도착한 데이터를 의미합니다.
Ex)
12:02에 발생했지만,12:01에 도착해서 순서가 뒤바뀐(out-of-order) 예시

발생 이유
1. 네트워크 지연
2. 버퍼링
3. 복잡한 데이터 경로
4. 모바일/IoT 기기에서의 오프라인 저장 후 업로드&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Flink 아키텍처, EcoSystem&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1035&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oR9Aa/dJMcaiW1O3f/xl7V6OZEWRz24uk3y4nxs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oR9Aa/dJMcaiW1O3f/xl7V6OZEWRz24uk3y4nxs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oR9Aa/dJMcaiW1O3f/xl7V6OZEWRz24uk3y4nxs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoR9Aa%2FdJMcaiW1O3f%2Fxl7V6OZEWRz24uk3y4nxs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1035&quot; height=&quot;430&quot; data-origin-width=&quot;1035&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o41HF/dJMcaakq1qr/XddddsmtKMOANDfUEVtqjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o41HF/dJMcaakq1qr/XddddsmtKMOANDfUEVtqjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o41HF/dJMcaakq1qr/XddddsmtKMOANDfUEVtqjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo41HF%2FdJMcaakq1qr%2FXddddsmtKMOANDfUEVtqjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;853&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;JobManager(Master node):&lt;/b&gt;&amp;nbsp;하나 이상의 TaskManager로 구성되어 있으며, 제출된 작업을 예약 및 관리하고 작업에 자원을 할당해 실행 계획을 조율&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Job Manager 데몬은 클러스터의&amp;nbsp;&lt;b&gt;마스터 노드&lt;/b&gt;에서 실행되며, Flink 시스템에서&amp;nbsp;&lt;b&gt;조정자(coordinator)&lt;/b&gt;&amp;nbsp;역할을 함&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Job Manager는&amp;nbsp;&lt;b&gt;클라이언트 시스템으로부터 프로그램 코드를 전달받아&lt;/b&gt;, 그 작업을&amp;nbsp;&lt;b&gt;슬레이브 노드(작업 노드)&lt;/b&gt;&amp;nbsp;에&amp;nbsp;&lt;b&gt;할당&lt;/b&gt;하여 처리를 수행&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;TaskManager(Slave node):&lt;/b&gt;&amp;nbsp;클러스터의 여러 노드에 걸쳐 할당된 자원에서 사용자 정의 기능을 실행&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Task Manager 데몬은 클러스터의&amp;nbsp;&lt;b&gt;슬레이브 노드(작업 노드)&lt;/b&gt;&amp;nbsp;에서 실행되며, Flink 시스템에서&amp;nbsp;&lt;b&gt;실제 연산을 수행하는 역할&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Job Manager로부터 명령을 전달받아&lt;/b&gt;,&amp;nbsp;&lt;b&gt;요구된 작업을 수행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 아키텍처의 장점은 대규모 데이터 세트를 거의 실시간으로 처리할 수 있도록 효율적으로 확장할 수 있다는 것&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Flink 실행 흐름&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;49&quot; data-end=&quot;79&quot;&gt;2-1. Program (프로그램 작성)&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;80&quot; data-end=&quot;128&quot;&gt;클라이언트 시스템이 실행을 위해 제출하는, 사용자가 개발한 애플리케이션 프로그램&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;135&quot; data-end=&quot;177&quot;&gt;2-2. Parse and Optimize (파싱 및 최적화)&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;178&quot; data-end=&quot;245&quot;&gt;이 단계에서는 코드를 파싱하여 문법 오류를 확인하고, 타입 추출(Type Extractor), 최적화 작업을 수행&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;252&quot; data-end=&quot;296&quot;&gt;2-3. DataFlow Graph (데이터 플로우 그래프 변환)&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;297&quot; data-end=&quot;357&quot;&gt;애플리케이션 작업이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;데이터 플로우 그래프&lt;/b&gt;로 변환되어 이후 실행 단계에서 사용할 수 있도록 준비&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;364&quot; data-end=&quot;399&quot;&gt;2-4. Job Manager (잡 매니저 처리)&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;400&quot; data-end=&quot;513&quot;&gt;이 단계에서 Flink의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Job Manager 데몬&lt;/b&gt;이 태스크를 스케줄링하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Task Manager&lt;/b&gt;에게 실행을 위임하고,&lt;br /&gt;&lt;b&gt;중간 처리 결과를 모니터링&lt;/b&gt;하는 역할도 수행&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;520&quot; data-end=&quot;558&quot;&gt;2-5. Task Manager (태스크 매니저 처리)&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;559&quot; data-end=&quot;615&quot;&gt;이 단계에서 Task Manager는 Job Manager가 할당한 작업을 실제로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;실행&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6lDJu/dJMcab4HU6U/RGO0YJfAIYjTxgQTSykym0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6lDJu/dJMcab4HU6U/RGO0YJfAIYjTxgQTSykym0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6lDJu/dJMcab4HU6U/RGO0YJfAIYjTxgQTSykym0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6lDJu%2FdJMcab4HU6U%2FRGO0YJfAIYjTxgQTSykym0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;683&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;EcoSystem&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;957&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7eLer/dJMcafsuVdr/0iX9JBfd2kgCnK6GZAJBR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7eLer/dJMcafsuVdr/0iX9JBfd2kgCnK6GZAJBR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7eLer/dJMcafsuVdr/0iX9JBfd2kgCnK6GZAJBR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7eLer%2FdJMcafsuVdr%2F0iX9JBfd2kgCnK6GZAJBR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;957&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;957&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DataSet API:&lt;/b&gt;&amp;nbsp;일괄 처리를 위한 Flink의 핵심 API로 Map, Reduce, Join, Co-group 같은 반복 연산에 사용&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DataStream API:&lt;/b&gt;&amp;nbsp;스트리밍 데이터(무제한 및 무한 라이브 데이터 스트림)를 처리하는 데 사용되며, 이를 통해 사용자는 외부 데이터 저장소를 쿼리하여 윈도우잉, 시간당 기록 변환, 이벤트 보강 등 들어오는 이벤트에 대한 임의의 연산을 정의할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;복합 이벤트 처리(CEP: Complex Event Processing)&lt;/b&gt;&amp;nbsp;정규식이나 StateMachine을 사용하여 이벤트 패턴을 지정해DataStream API와 통합되어&amp;nbsp;&lt;b&gt;데이터에 대한 패턴 인식을 실시간으로 수행&lt;/b&gt;할 수 있음. 네트워크 이상 탐지, 규칙 기반 알림, 프로세스 모니터링, 사기 탐지 같은 애플리케이션에 사용&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SQL 및 Table API:&lt;/b&gt;&amp;nbsp;SQL 쿼리와 Table API를 사용해 테이블 스키마를 기반으로 데이터를 쉽게 조작하여 최소한의 노력으로 복잡한 데이터 변환 파이프라인을 구축할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Gelly&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DataSet API 위에서 실행되는 다목적 그래프 처리 및 분석 라이브러리로 확장성과 견고함을 모두 갖추고 있음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Gelly는 label propagation, triangle enumeration, page rank와 같은 기본 제공 알고리즘을 갖추고 있으며 쉽게 구현할 수 있는 사용자 정의&amp;nbsp;&lt;b&gt;그래프 알고리즘 API도 지원&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;FlinkML&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DataSet API 위에서 실행되는 분산 머신 러닝 알고리즘 라이브러리로 선형 회귀, 로지스틱 회귀, 의사 결정 트리, K-평균 클러스터링, LDA 등과 같은 지도 및 비지도 학습 기법을 모두 적용할 수 있는 통합된 방법을 사용자에게 제공&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;신경망 구축을 위한 실험적인 딥 러닝 프레임워크(TensorFlow 패키징)를 제공&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Window&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;내부적으로 어떻게 flink가 데이터를 처리하는지를 확인하려면 Window를 살펴봐야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG2Bup/dJMcaduC4Gq/4qyz4zAupCNdapgZhGguoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG2Bup/dJMcaduC4Gq/4qyz4zAupCNdapgZhGguoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG2Bup/dJMcaduC4Gq/4qyz4zAupCNdapgZhGguoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG2Bup%2FdJMcaduC4Gq%2F4qyz4zAupCNdapgZhGguoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;741&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Apache Flink에서 윈도우는 위 그림과 같은 라이프 사이클을 가짐&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하나의 윈도우는 입력 스트림(Input Stream)을 소스로 받고 출력 스트림(Output Stream)을 생성하는 큰 구조로 이루어져 있고, 이렇게 생성된 출력 스트림은 또 다른 곳에서 입력 스트림이 됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1290&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;윈도우 할당자 (Window Assigner)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;들어온 데이터를 하나 이상의 윈도우에 할당하는 역할&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Flink에는 기본적으로 텀블링 윈도우, 슬라이딩 윈도우, 세션 윈도우 글로벌 윈도우 4개의 기본 윈도우 할당자를 제공&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;ee6f&quot; style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;윈도우 할당자는 입력 데이터를 보고 현재 조건에 만족하는 윈도우가 없으면 새로운 윈도우를 생성하고 데이터를 삽입&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하나의 윈도우는 데이터가 모두 담겨있다는 확신이 들면 윈도우 함수를 먹여서 데이터에 연산을 수행하여 데이터를 변환하고 윈도우를 삭제&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;94c1&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;트리거 (Trigger)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;윈도우는 하나의&amp;nbsp;트리거와&amp;nbsp;윈도우 함수(Window Function)&amp;nbsp;를 가지고 있음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수에는 윈도우에 들어온 데이터에 대해 어떤 연산을 적용할 것인지 정의하고, 트리거는 언제 윈도우에 있는 데이터로 연산을 수행할 지 결정하는 역할을 함&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들면, 윈도우 안에 있는 데이터가 4개 이상이라면 혹은 워터마크가 윈도우 종점을 통과했다면 이라는 조건을 달 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;d44b&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;소멸자 (Evictor)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트리거가 발생해서 윈도우를 구체화 하기 전에, 데이터 일부를 연산에서 제외시키도록 결정하는 역할(필터)&lt;/span&gt;&lt;/p&gt;
&lt;h4 id=&quot;e3f3&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Keyed or Non-Keyed Window&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Flink에서는&amp;nbsp;keyBy&amp;nbsp;함수로 데이터의 특정 값을 키로 잡을 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 키를 잡지 않으면 하나의 세션에서 처리, 키를 잡으면 병렬로 처리(카프카에서 PartitionKey)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4가지 기본 Window&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1) 텀블링 윈도우 (Tumbling Window)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고정 크기 윈도우라고도 부르며 일정한 크기로 윈도우를 할당, 데이터의 개수가 될수도 있고 시간이 될수도 있음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;윈도우의 기준을 이벤트 시간으로 나눌지 프로세싱 시간으로 나눌지도 결정할 수 있고 시간도 밀리초 단위까지 세분화 해서 윈도우를 자를 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf8BF5/dJMcabDEvmK/Pab6cFTFT8e0o9k5nAeyyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf8BF5/dJMcabDEvmK/Pab6cFTFT8e0o9k5nAeyyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf8BF5/dJMcabDEvmK/Pab6cFTFT8e0o9k5nAeyyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf8BF5%2FdJMcabDEvmK%2FPab6cFTFT8e0o9k5nAeyyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;618&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2) 슬라이딩 윈도우(Sliding Window)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;슬라이딩 윈도우 또한 고정 크기의 윈도우를 가지는데,&amp;nbsp;window slide parameter&amp;nbsp;를 지정해서 윈도우가 생성되는 빈도를 결정&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 크기와 생성 빈도수를 결정하기 때문에 빈도수를 크기보다 작은 값으로 주면 데이터가 여러 윈도우에 겹치게 됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;597&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjYoNT/dJMcafTAwNV/hkE1MfIGz7z2GqvYW6TUJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjYoNT/dJMcafTAwNV/hkE1MfIGz7z2GqvYW6TUJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjYoNT/dJMcafTAwNV/hkE1MfIGz7z2GqvYW6TUJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjYoNT%2FdJMcafTAwNV%2FhkE1MfIGz7z2GqvYW6TUJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;597&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;597&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3) 세션 윈도우 (Session Window)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;세션 윈도우는 특이하게 특정 시간 동안 데이터가 나타나지 않으면 구체화 하는 윈도우&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유저 단위로 프로세싱하려고 할 때 사용할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WH9h7/dJMcabwRpOl/N87WT84WmtyMfrz7vAyyk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WH9h7/dJMcabwRpOl/N87WT84WmtyMfrz7vAyyk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WH9h7/dJMcabwRpOl/N87WT84WmtyMfrz7vAyyk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWH9h7%2FdJMcabwRpOl%2FN87WT84WmtyMfrz7vAyyk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;587&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;587&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;4) 글로벌 윈도우(Global Window)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;글로벌 윈도우는 같은 키를 가진 모든 데이터를 하나의 전역 윈도우에 담음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;글로벌 윈도우는 반드시 커스텀 트리거를 정의해서 사용해야 함, 그러지 않으면 아무런 역할도 하지 않음. 왜냐면 윈도우에 끝 지점이라는게 존재하지 않아서 기본 트리거는 아무런 Firing 도 안하기 때문&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/803b9/dJMcaarc88Q/grVh07VMskofe8JXH9iiek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/803b9/dJMcaarc88Q/grVh07VMskofe8JXH9iiek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/803b9/dJMcaarc88Q/grVh07VMskofe8JXH9iiek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F803b9%2FdJMcaarc88Q%2FgrVh07VMskofe8JXH9iiek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;588&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;WaterMark&lt;/h2&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;WaterMark의 중요성은 데이터 처리에 있어서 중요한 역할을 하기 때문에 그 개념과 어떤식으로 사용하는지, 다른 시스템에는 존재하는지 확인하고 비교해보겠습니다. 해당 개념은 이렇게 보면 간단합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774768595129&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;워터마크: 지연된 이벤트(out-of-order data)를 다루기 위한 기준 시점

스트리밍 시스템은 실시간으로 들어오는 데이터를 일정 단위(윈도우)로 모아 처리하는데, 실제 발생한 시각과 도착한 시각의 차이는 발생할 수 있기 때문에
이벤트 시간(Event Time) 을 기준으로 처리하되, &quot;언제쯤 데이터를 마감해도 될지&quot; 알려주는 장치가 바로 워터마크

(실제 세상에서는 10:09 의 데이터가 10:11 에 도착할 수 있기 때문에 인입 시간을 기준으로 윈도우를 구체화 하는 건 데이터가 누락 될 위험이 굉장히 커집니다.)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbNy8W/dJMcabDEvpa/6xN3eSAycCOOhAV7tIYAeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbNy8W/dJMcabDEvpa/6xN3eSAycCOOhAV7tIYAeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbNy8W/dJMcabDEvpa/6xN3eSAycCOOhAV7tIYAeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbNy8W%2FdJMcabDEvpa%2F6xN3eSAycCOOhAV7tIYAeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;462&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;워터마크는 대체로 휴리스틱(추정)하며, 항상 정확하지는 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;F(P) -&amp;gt; E&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;의 형태로 인입 시간을 받으면 이벤트 시간을 리턴하는 함수로 표현할 수 있습니다. 반환된 E는 지금 이 순간 부터 E 이전에 존재하는 모든 데이터를 받았다는 의미로, 다르게 말하면 앞으로는 E 이전의 시간을 가진 데이터가 입력으로 주어지는 일이 없다는 말이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;완벽한 워터마크를 만들려면?&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li id=&quot;4ccd&quot; style=&quot;list-style-type: decimal; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;Apache Kafka 처럼 부분 순서를 보장하는 시스템이 존재하고 이벤트를 완벽히 시간 순서대로 순차적으로 쌓은 경우&lt;/b&gt;&lt;br /&gt;(애초에 순서대로 데이터가 도착한다는 보장이 있으면 완벽한 워터마크를 쉽게 만들 수 있음)&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;그냥 프로세싱 시간을 기준으로 윈도우를 구체화&lt;/b&gt;&lt;br /&gt;(이벤트 시간을 무시하는 것이기 때문에 완벽한 워터마크를 만들 수 있음)&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;위 두가지 경우가 아니라면 워터마크는 항상 휴리스틱 할 수 밖에 없고 완벽한 계산을 요구하는 비즈니스라면 늦게 들어온 데이터(lateness)를 어떻게 처리해야 하는가를 고민해야 함&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzUvya/dJMcafMQgc4/svAUr7nLfB6cazJBA4LBs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzUvya/dJMcafMQgc4/svAUr7nLfB6cazJBA4LBs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzUvya/dJMcafMQgc4/svAUr7nLfB6cazJBA4LBs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzUvya%2FdJMcafMQgc4%2FsvAUr7nLfB6cazJBA4LBs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;888&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1055&quot; data-end=&quot;1114&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;워터마크는 시스템이&amp;nbsp;&lt;b&gt;데이터를 수집하는 과정 중에 계속 갱신&lt;/b&gt;합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 이벤트가 도착할 때마다:&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot; data-start=&quot;1116&quot; data-end=&quot;1206&quot;&gt;
&lt;li style=&quot;list-style-type: decimal; color: #666666;&quot; data-start=&quot;1116&quot; data-end=&quot;1142&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;그 이벤트의 이벤트 시간&lt;/b&gt;을 확인하고&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #666666;&quot; data-start=&quot;1143&quot; data-end=&quot;1168&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;가장 늦은 이벤트 시간&lt;/b&gt;을 추적하고&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #666666;&quot; data-start=&quot;1169&quot; data-end=&quot;1206&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;(그 시간 - 허용 지연 시간)&lt;/b&gt;&amp;nbsp;만큼을 워터마크로 설정&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1774768647601&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Watermark = max_event_time_seen_so_far - allowed_lateness

허용 지연시간
현실 세계에서 이벤트가 &quot;얼마나 늦게&quot; 도착할 수 있을지를 예상해서 정한 시간 범위입니다.
이 시간 내에 들어온늦은 데이터는 윈도우에 포함되도록 허용하고, 그 이후에 오는 데이터는 버리거나(side output) 별도로 처리&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;워터마크 방식과 코드&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;907&quot; data-end=&quot;953&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Bounded Out-of-Orderness (가장 일반적)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;954&quot; data-end=&quot;1030&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;954&quot; data-end=&quot;987&quot;&gt;&lt;b&gt;이벤트가 max X초 늦게 도착할 수 있음&lt;/b&gt;을 가정&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;988&quot; data-end=&quot;1030&quot;&gt;Watermark = max(event.timestamp) - delay&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;1032&quot; data-end=&quot;1080&quot;&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Punctuated Watermarks (데이터 기반 워터마크)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;1081&quot; data-end=&quot;1109&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;1081&quot; data-end=&quot;1109&quot;&gt;특정 조건을 만족하는 이벤트에서 워터마크를 추출&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;1111&quot; data-end=&quot;1147&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Idleness (Stream 정체 대응)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;1148&quot; data-end=&quot;1186&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;1148&quot; data-end=&quot;1186&quot;&gt;소스가 일정 시간 이벤트를 안 보낼 경우 워터마크 전파 중단 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;JAVA code&lt;/h4&gt;
&lt;pre id=&quot;code_1774768671460&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DataStream&amp;lt;Event&amp;gt; stream = env
  .socketTextStream(&quot;localhost&quot;, 9999)
  .map(line -&amp;gt; parseEvent(line))
  .assignTimestampsAndWatermarks(
    WatermarkStrategy.&amp;lt;Event&amp;gt;forBoundedOutOfOrderness(Duration.ofSeconds(5))
      .withTimestampAssigner((event, timestamp) -&amp;gt; event.getEventTime())
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flink Table, SQL API&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774768690884&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE sensor_data (
  id STRING,
  ts TIMESTAMP(3),
  value DOUBLE,
  WATERMARK FOR ts AS ts - INTERVAL '5' SECOND
) WITH (
  'connector' = 'kafka',
  ...
);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SparkStreaming&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SparkStreaming도 워터마크를 지원하는데&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;b&gt;withWatermark()&lt;/b&gt;&amp;nbsp;&lt;/span&gt;메소드를 사용해서 WaterMark를 지원함&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이벤트 시간 기반 처리 가능&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정확한 처리 위해 late event 보존 기간 지정 필요&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;6. Exactly once&lt;/h2&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Exactly-once가 중요한 이유는 위에서도 언급을 했는데, flink는 어떻게 exactly-once를 구현하는지를 확인해보고, 다른 시스템과 비교를 해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;스트리밍 시스템들을 보면&amp;nbsp;&lt;/span&gt;&lt;b&gt;정확히 한 번 처리(Exactly Once)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;하는 것을 아주 중요하게 강조하고 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;윈도우가 정확히 한 번 구체화 되었는가?&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터가 중복으로 들어가진 않았는가?&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;4d2a&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 윈도우가 정확히 한 번 구체화 되었는가?&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;윈도우 함수 자체는 멱등(idempotent)하지 않을 수 있기 때문에 한 번만 구체화 되는 것이 중요함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;533&quot; data-end=&quot;547&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비결정적인 특성&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;549&quot; data-end=&quot;603&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어 처리 중간에&amp;nbsp;&lt;b&gt;랜덤 수를 생성하거나&lt;/b&gt;,&amp;nbsp;&lt;b&gt;현재 시각을 사용하는 로직&lt;/b&gt;이 있다면:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;605&quot; data-end=&quot;658&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;605&quot; data-end=&quot;624&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장애 이전엔 랜덤값 A,&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;625&quot; data-end=&quot;658&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장애 이후 재처리에서는 랜덤값 B &amp;rarr; ❌ 결과가 다름&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;665&quot; data-end=&quot;697&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;체크포인트&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;699&quot; data-end=&quot;733&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Flink는 주기적으로 다음을&amp;nbsp;&lt;b&gt;체크포인트에 저장&lt;/b&gt;(&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;Amazon S3에 체크포인트를 저장하고 관리)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;735&quot; data-end=&quot;787&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;735&quot; data-end=&quot;765&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;연산 중이던 연산자들의&amp;nbsp;&lt;b&gt;상태(state)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;766&quot; data-end=&quot;787&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처리하던 데이터의 위치(오프셋 등)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;789&quot; data-end=&quot;801&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;결과적으로&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot; data-start=&quot;803&quot; data-end=&quot;894&quot;&gt;
&lt;li style=&quot;list-style-type: decimal; color: #666666;&quot; data-start=&quot;803&quot; data-end=&quot;834&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장애 발생 시,&amp;nbsp;&lt;b&gt;과거 저장된 상태로 되돌아감&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #666666;&quot; data-start=&quot;835&quot; data-end=&quot;858&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;같은 상태 + 같은 입력을 다시 처리&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #666666;&quot; data-start=&quot;859&quot; data-end=&quot;894&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 결과는&amp;nbsp;&lt;b&gt;항상 동일하게 유지됨 = 결정적 처리&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf0ejy/dJMcacJlkW2/kTe2CxYGlV02PVxA7mT3Vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf0ejy/dJMcacJlkW2/kTe2CxYGlV02PVxA7mT3Vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf0ejy/dJMcacJlkW2/kTe2CxYGlV02PVxA7mT3Vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf0ejy%2FdJMcacJlkW2%2FkTe2CxYGlV02PVxA7mT3Vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;581&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;581&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;76e8&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;데이터가 중복으로 들어가진 않았는가?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이는 간단하게 데이터에 고유 ID를 부여해서 윈도우에 중복된 데이터가 들어가 있는지 아닌지만 검증해서 해결할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터의 중복은 배치, 스트리밍 구분없이 항상 중요한 문제이기 때문에, 중복의 후 처리 로직이 있지 않는 이상, 한 번만 정확히 실행(Exactly once)되는 것이 중요함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;exactly once를 구현하는 방법, property 예시&lt;/h3&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;kafka 및 sparkstreaming은 조건부로 exactly once를 지원하는 것에 반해 flink는 checkpoint + 상태관리로 Exactly Once를 보장합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Flink가 exactly once를 구현하는 방법&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;1182&quot; data-end=&quot;1219&quot;&gt;Flink는 일정 간격으로 전체 파이프라인의 상태를 체크포인팅&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;1220&quot; data-end=&quot;1292&quot;&gt;Sink 연산(예: Kafka, DB 등)도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;2-phase commit&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;idempotent 방식&lt;/b&gt;으로 구성&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot; data-start=&quot;1293&quot; data-end=&quot;1331&quot;&gt;실패 시 체크포인트 지점부터 복구하여,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;중복 없이 재처리&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;flink는 아래 3가지의 처리 보장 수준을 지원하며, 변경할 수도 있습니다.&lt;/h4&gt;
&lt;table style=&quot;color: #666666; text-align: center; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;모드&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;보장&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;AT_MOST_ONCE&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최대 한 번 처리&lt;/td&gt;
&lt;td&gt;빠르지만 유실 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;AT_LEAST_ONCE&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최소 한 번 처리&lt;/td&gt;
&lt;td&gt;유실 없음, 중복 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;EXACTLY_ONCE&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;정확히 한 번 처리&lt;/td&gt;
&lt;td&gt;가장 안전, Flink의 기본값&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;777&quot; data-end=&quot;807&quot;&gt;Flink에서 처리 보장 수준 변경 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; Java/Scala 코드 설정 &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774768801824&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

// 체크포인팅 주기 설정 (필수)
env.enableCheckpointing(5000); // 5초

// 처리 보장 수준 변경 가능
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.AT_MOST_ONCE);
// 또는
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.AT_LEAST_ONCE);
// 또는
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); // 기본값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; Flink 설정 파일 (flink-conf.yaml)에서 설정 &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774768816399&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;execution.checkpointing.mode: EXACTLY_ONCE   # 또는 AT_LEAST_ONCE / AT_MOST_ONCE
execution.checkpointing.interval: 5000ms     # 체크포인트 주기 (필수)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flink</category>
      <category>Ecosystem</category>
      <category>flink</category>
      <category>데이터엔지니어</category>
      <category>스트리밍시스템</category>
      <category>스트림데이터</category>
      <author>jyu_seo_</author>
      <guid isPermaLink="true">https://jyu-seo.tistory.com/183</guid>
      <comments>https://jyu-seo.tistory.com/183#entry183comment</comments>
      <pubDate>Sun, 29 Mar 2026 16:22:04 +0900</pubDate>
    </item>
    <item>
      <title>[Spark] - Spark Performance Tuning</title>
      <link>https://jyu-seo.tistory.com/182</link>
      <description>&lt;p data-end=&quot;279&quot; data-start=&quot;186&quot; data-ke-size=&quot;size16&quot;&gt;Spark를 사용하다 보면 단순한 코드 작성보다 더 중요한 것이 있습니다.&lt;br /&gt;바로 &lt;b&gt;데이터를 어떻게 나누고, 어떻게 이동시키고, 어떻게 실행하느냐&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;345&quot; data-start=&quot;284&quot; data-ke-size=&quot;size16&quot;&gt;같은 코드라도 Partition, Shuffle, Join 전략에 따라 성능은 몇 배 이상 차이가 납니다.&lt;/p&gt;
&lt;p data-end=&quot;454&quot; data-start=&quot;350&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는 Spark의 내부 실행 구조를 기반으로&lt;br /&gt;&lt;b&gt;Compute, Data, Execution, Skew, AQE까지 이어지는 실무 성능 튜닝 프레임워크&lt;/b&gt;를 정리합니다.&lt;/p&gt;
&lt;p data-end=&quot;536&quot; data-start=&quot;459&quot; data-ke-size=&quot;size16&quot;&gt;단순 개념이 아니라, &lt;b&gt;&amp;ldquo;왜 느려지는지 &amp;rarr; 어디를 봐야 하는지 &amp;rarr; 어떻게 해결하는지&amp;rdquo; &lt;/b&gt;흐름 중심으로 정리해보려 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spark Performance Tuning&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 분배, 이동, 실행 방식을 최적화하여 처리 속도와 자원 효율을 개선하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spark 튜닝 프레임워크&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774677136001&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Spark Performance Tuning
 
1. Compute Layer (연산 구조 최적화)
   - Partition
   - Shuffle
   - Join Strategy
   	 - Broadcast Hash Join
	 - Sort Merge Join
   - Repartition / Coalesce 전략
   - Narrow vs Wide 구분
   - DAG / Stage 구조 이해
2. Data Layer (I/O 최적화)
   - Format (Parquet)
   - Partitioning
   - File Size
   - Column Pruning
   - Predicate Pushdown
   - Partition Pruning
   - Small file 문제
3. Execution Layer (실행 효율)
	- cache() / persist()
	- unpersist()
	- StorageLevel 선택
	- Memory tuning
    - Execution vs Storage memory
	- spill 방지
	- off-heap memory
4. Skew Handling (병목 제거)
   - Salting
   - Broadcast
   - AQE Skew
   - skew detection (Spark UI)
   - heavy key 분리 처리
5. Runtime Optimization (자동 최적화)
   - AQE
    - Skew join handling
    - Join strategy 변경
    - Partition coalescing
    
Spark Performance Tuning 전체구조

1. Compute Layer        &amp;rarr; 연산 구조
2. Data Layer           &amp;rarr; I/O 최적화
3. Execution Layer      &amp;rarr; 메모리 &amp;amp; 재사용
4. Skew Handling        &amp;rarr; 병목 제거
5. Runtime Optimization &amp;rarr; AQE 자동 최적화&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.Compute Layer&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Compute Layer는 Spark에서 데이터 처리 방식과 연산 전략을 결정하는 계층으로, Partitioning, Shuffle, Join Strategy, DAG 구조 등을 통해 병렬 처리와 성능에 직접적인 영향을 줍니다&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-size: 16px; letter-spacing: 0px;&quot;&gt;Spark의 &lt;/span&gt;&lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;0&quot;&gt;Compute Layer&lt;/b&gt;&lt;span style=&quot;font-size: 16px; letter-spacing: 0px;&quot;&gt; 최적화는 결국 &lt;b&gt;데이터 이동(Shuffle)&lt;/b&gt;을 최소화하고 병렬성을 극대화하는 것에 집중됩니다. 실무에서 가장 자주 쓰이는 최적화 기법들을 PySpark 코드 예시와 함께 보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Partition (데이터 분할 단위)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-31 105812.png&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4ObKi/dJMcafMRvXq/VcQMeyOWT96kuBzv1vse0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4ObKi/dJMcafMRvXq/VcQMeyOWT96kuBzv1vse0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4ObKi/dJMcafMRvXq/VcQMeyOWT96kuBzv1vse0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4ObKi%2FdJMcafMRvXq%2FVcQMeyOWT96kuBzv1vse0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;979&quot; height=&quot;780&quot; data-filename=&quot;화면 캡처 2026-03-31 105812.png&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;파티션을 쪼개는 명령어&lt;/h4&gt;
&lt;pre id=&quot;code_1774682366233&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 현재 파티션 개수 확인
print(f&quot;Current Partitions: {df.rdd.getNumPartitions()}&quot;)

# 2. 파티션 강제 재분배 (Repartition) - 전체 데이터를 셔플하여 균등하게 나눔
# CPU 코어 수의 2~3배 정도로 설정하는 것이 일반적입니다.
df_repartitioned = df.repartition(10)

# 3. 파티션 축소 (Coalesce) - 셔플을 최소화하며 파티션 수를 줄임 (저장 직전 권장)
df_coalesced = df.coalesce(2)

# 4. 특정 컬럼 기준으로 파티션 나누기 (데이터 스큐 방지 및 조인 최적화)
df_by_key = df.repartition(10, &quot;user_id&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-31 112257.png&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clCOPA/dJMcahYa55E/pldkTjyX5sXJpCaBRGhs1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clCOPA/dJMcahYa55E/pldkTjyX5sXJpCaBRGhs1K/img.png&quot; data-alt=&quot;파티션을 repartition으로 쪼개서 파티션이 몇초 걸리는지 print하는 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clCOPA/dJMcahYa55E/pldkTjyX5sXJpCaBRGhs1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclCOPA%2FdJMcahYa55E%2FpldkTjyX5sXJpCaBRGhs1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;289&quot; data-filename=&quot;화면 캡처 2026-03-31 112257.png&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;파티션을 repartition으로 쪼개서 파티션이 몇초 걸리는지 print하는 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-31 112348.png&quot; data-origin-width=&quot;1447&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IVe1s/dJMcah4XEPV/C1lbQapYIw1LCJSxBKy7UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IVe1s/dJMcah4XEPV/C1lbQapYIw1LCJSxBKy7UK/img.png&quot; data-alt=&quot;coalesce() 파티션개수를 줄일때 사용한다. 실행결과 이전에는 1000개로나눴을때 30초가 걸리던게 coalesce를 사용하면 1.82초가 걸린다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IVe1s/dJMcah4XEPV/C1lbQapYIw1LCJSxBKy7UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIVe1s%2FdJMcah4XEPV%2FC1lbQapYIw1LCJSxBKy7UK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1447&quot; height=&quot;413&quot; data-filename=&quot;화면 캡처 2026-03-31 112348.png&quot; data-origin-width=&quot;1447&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;coalesce() 파티션개수를 줄일때 사용한다. 실행결과 이전에는 1000개로나눴을때 30초가 걸리던게 coalesce를 사용하면 1.82초가 걸린다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-31 110006.png&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B7fqa/dJMb996U5uC/RCfVd8vI5JrFEhouOBUu2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B7fqa/dJMb996U5uC/RCfVd8vI5JrFEhouOBUu2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B7fqa/dJMb996U5uC/RCfVd8vI5JrFEhouOBUu2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB7fqa%2FdJMb996U5uC%2FRCfVd8vI5JrFEhouOBUu2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;899&quot; height=&quot;532&quot; data-filename=&quot;화면 캡처 2026-03-31 110006.png&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-31 112348.png&quot; data-origin-width=&quot;1447&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFUtHQ/dJMcagLK8BQ/N28oyngK0taqpHJi0DQVJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFUtHQ/dJMcagLK8BQ/N28oyngK0taqpHJi0DQVJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFUtHQ/dJMcagLK8BQ/N28oyngK0taqpHJi0DQVJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFUtHQ%2FdJMcagLK8BQ%2FN28oyngK0taqpHJi0DQVJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1447&quot; height=&quot;413&quot; data-filename=&quot;화면 캡처 2026-03-31 112348.png&quot; data-origin-width=&quot;1447&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티션은 Spark가 병렬로 처리하는 &lt;b data-index-in-node=&quot;21&quot; data-path-to-node=&quot;4&quot;&gt;최소 작업 단위&lt;/b&gt;입니다. 파티션 하나당 CPU 코어(Task) 하나가 할당됩니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Repartition vs Coalesce (파티션 제어)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터의 병렬 처리 단위인 파티션을 어떻게 관리하느냐에 따라 리소스 효율이 달라집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;9,0,0&quot; data-index-in-node=&quot;0&quot;&gt;repartition(n)&lt;/b&gt;: 데이터를 전체적으로 다시 섞어(Full Shuffle) 파티션을 균등하게 나눕니다. 병렬성을 높여야 할 때 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;9,1,0&quot; data-index-in-node=&quot;0&quot;&gt;coalesce(n)&lt;/b&gt;: 셔플을 최소화하며 파티션 수를 줄입니다. 주로 필터링 후 데이터가 작아졌을 때 파일을 저장하기 직전 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1775191948379&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 병렬성이 부족하여 태스크를 늘려야 할 때 (Wide Transformation 전)
df = df.repartition(200) 

# 2. 데이터 필터링 후 파티션이 너무 많아져서 파일 개수를 줄여야 할 때 (저장 직전)
df.filter(&quot;region = 'KR'&quot;) \
  .coalesce(10) \
  .write.parquet(&quot;output_path&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최적의 파티션 수 공식&lt;/h4&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;보통 Spark UI의 Stage 탭에서 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;8&quot;&gt;Task의 실행 시간&lt;/b&gt;을 보고 판단합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;Task가 너무 빨리 끝남 (100ms 이하):&lt;/b&gt; 파티션이 너무 많음 (스케줄링 오버헤드 발생).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;Task가 너무 오래 걸림 (수 분 이상):&lt;/b&gt; 파티션이 너무 적음 (메모리 부족 위험).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;가이드라인:&lt;/b&gt; 파티션당 데이터 크기를 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;9,2,0&quot;&gt;128MB ~ 256MB&lt;/b&gt; 정도로 유지하는 것이 가장 이상적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;파티션의 기본용량&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-31 111537.png&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;535&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sq9Xp/dJMcahcNViX/F2iTw3KZK3lISJW1gP4FF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sq9Xp/dJMcahcNViX/F2iTw3KZK3lISJW1gP4FF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sq9Xp/dJMcahcNViX/F2iTw3KZK3lISJW1gP4FF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSq9Xp%2FdJMcahcNViX%2FF2iTw3KZK3lISJW1gP4FF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;811&quot; height=&quot;535&quot; data-filename=&quot;화면 캡처 2026-03-31 111537.png&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;535&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Shuffle (데이터 재배치)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셔플은 조인(Join),그룹화(GroupBy),재분배(Repartition) 등 &lt;b data-index-in-node=&quot;47&quot; data-path-to-node=&quot;12&quot;&gt;데이터의 위치를 옮겨야 할 때&lt;/b&gt; 발생합니다. Spark에서 가장 비용이 많이 드는(가장 느린) 작업입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 셔플을 유발하는 연산 (Wide Transformation)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774682474799&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. GroupBy: 동일한 키를 가진 데이터를 한곳으로 모아야 함 (Shuffle 발생)
df_grouped = df.groupBy(&quot;department&quot;).avg(&quot;salary&quot;)

# 2. Join: 서로 다른 테이블의 같은 키를 매칭하기 위해 데이터를 이동 (Shuffle 발생)
df_joined = df1.join(df2, &quot;employee_id&quot;)

# 3. Distinct: 중복 제거를 위해 데이터를 비교해야 함 (Shuffle 발생)
df_unique = df.select(&quot;user_id&quot;).distinct()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;셔플 최적화 핵심 팁&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;1.Shuffle Write/Read 줄이기:&lt;/b&gt; Spark UI에서 Shuffle Write 값이 큰 스테이지를 찾으세요. 데이터 필터링(filter)이나 컬럼 선택(select)을 셔플 연산 &lt;b data-index-in-node=&quot;105&quot; data-path-to-node=&quot;16,0,0&quot;&gt;이전&lt;/b&gt;에 수행하여 이동하는 데이터 양 자체를 줄여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;2.spark.sql.shuffle.partitions 설정:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본값은 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;16,1,1,0,0&quot;&gt;200&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;데이터가 아주 작으면 이 값을 줄여야 하고(예: 10~50), 데이터가 수 TB 단위라면 늘려야 합니다(예: 1000~2000).&lt;/li&gt;
&lt;li&gt;AQE(Adaptive Query Execution)를 켜면 Spark가 런타임에 이 값을 자동으로 조절해 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774682539943&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spark.conf.set(&quot;spark.sql.adaptive.enabled&quot;, &quot;true&quot;)
spark.conf.set(&quot;spark.sql.adaptive.coalescePartitions.enabled&quot;, &quot;true&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Partition과 Shuffle의 관계&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;19&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;개념&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;성능 영향&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0,0&quot;&gt;Partition&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,1,1,0&quot;&gt;데이터의 조각&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,1,2,0&quot;&gt;너무 적으면 OOM(메모리 부족), 너무 많으면 관리 부하 증가&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0,0&quot;&gt;Shuffle&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,2,1,0&quot;&gt;파티션 간 데이터 이동&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,2,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,2,0&quot;&gt;성능 저하의 주범&lt;/b&gt;. 네트워크/디스크 I/O 발생&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 195039.png&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/exJkNF/dJMcad2usDD/dzY4OQiU8FUAyaMxNeEvn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/exJkNF/dJMcad2usDD/dzY4OQiU8FUAyaMxNeEvn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/exJkNF/dJMcad2usDD/dzY4OQiU8FUAyaMxNeEvn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FexJkNF%2FdJMcad2usDD%2FdzY4OQiU8FUAyaMxNeEvn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;372&quot; height=&quot;240&quot; data-filename=&quot;화면 캡처 2026-03-30 195039.png&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Join Strategy: Broadcast Hash Join (BHJ)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 200545.png&quot; data-origin-width=&quot;1573&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckHjJQ/dJMcabcy6yo/AYVPY8BXXdHHJLJV9oBVak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckHjJQ/dJMcabcy6yo/AYVPY8BXXdHHJLJV9oBVak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckHjJQ/dJMcabcy6yo/AYVPY8BXXdHHJLJV9oBVak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckHjJQ%2FdJMcabcy6yo%2FAYVPY8BXXdHHJLJV9oBVak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1573&quot; height=&quot;636&quot; data-filename=&quot;화면 캡처 2026-03-30 200545.png&quot; data-origin-width=&quot;1573&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 큰 데이터와 진짜 작은 데이터의 join일 경우 효율적인 join방식 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량 데이터 같은 경우에는 여러노드에 걸처서 데이터가 분산 저장되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;join하고자 하는 작은데이터도 굳이 쪼개서 저장하게 될경우 또 셔플이 일어나게되고 정렬과정이 일어나면서 리소스를 많이 잡아먹게 된다. 작은 용량의 데이터 같은경우 모든 워커로 복사를 하게됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 200921.png&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/umMv7/dJMcacvPbUo/9xZ1w7TytVAX7rWIv9d280/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/umMv7/dJMcacvPbUo/9xZ1w7TytVAX7rWIv9d280/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/umMv7/dJMcacvPbUo/9xZ1w7TytVAX7rWIv9d280/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FumMv7%2FdJMcacvPbUo%2F9xZ1w7TytVAX7rWIv9d280%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;872&quot; height=&quot;586&quot; data-filename=&quot;화면 캡처 2026-03-30 200921.png&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셔플이 일어나지 않고 join이 될수있도록 만드는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 201032.png&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;589&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzwCav/dJMcaaxZQTn/kRysPJfIZJlato8ofkeMDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzwCav/dJMcaaxZQTn/kRysPJfIZJlato8ofkeMDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzwCav/dJMcaaxZQTn/kRysPJfIZJlato8ofkeMDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzwCav%2FdJMcaaxZQTn%2FkRysPJfIZJlato8ofkeMDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;915&quot; height=&quot;589&quot; data-filename=&quot;화면 캡처 2026-03-30 201032.png&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;589&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 201221.png&quot; data-origin-width=&quot;1585&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2P4Bk/dJMcaaxZQUr/oKzYSDLmfxa8BKzGB96iy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2P4Bk/dJMcaaxZQUr/oKzYSDLmfxa8BKzGB96iy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2P4Bk/dJMcaaxZQUr/oKzYSDLmfxa8BKzGB96iy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2P4Bk%2FdJMcaaxZQUr%2FoKzYSDLmfxa8BKzGB96iy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1585&quot; height=&quot;382&quot; data-filename=&quot;화면 캡처 2026-03-30 201221.png&quot; data-origin-width=&quot;1585&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로는 10MB이하로 디폴트로 고정이 되어있습니다. 이 기본값은 Spark설정에서 바꿀수있지만 10MB이하일경우에는 기본적으로 Broadcast join이 되도록 설정이 되어있습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 201508.png&quot; data-origin-width=&quot;1731&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nvdjU/dJMcaiJvE60/f3zg1BtDpEg2LHhbk58fc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nvdjU/dJMcaiJvE60/f3zg1BtDpEg2LHhbk58fc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nvdjU/dJMcaiJvE60/f3zg1BtDpEg2LHhbk58fc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnvdjU%2FdJMcaiJvE60%2Ff3zg1BtDpEg2LHhbk58fc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1731&quot; height=&quot;519&quot; data-filename=&quot;화면 캡처 2026-03-30 201508.png&quot; data-origin-width=&quot;1731&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;print를 해보게되면 10MB가 true로 찍혀있는모습을 확인할수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 201608.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;229&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/06i1M/dJMcabDFshD/fERSH7Zc3fkKSMmO15t1zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/06i1M/dJMcabDFshD/fERSH7Zc3fkKSMmO15t1zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/06i1M/dJMcabDFshD/fERSH7Zc3fkKSMmO15t1zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F06i1M%2FdJMcabDFshD%2FfERSH7Zc3fkKSMmO15t1zk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1134&quot; height=&quot;229&quot; data-filename=&quot;화면 캡처 2026-03-30 201608.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;229&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;broadcast&amp;nbsp; 튜닝기법&lt;/h4&gt;
&lt;pre id=&quot;code_1774681649477&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import broadcast

# 큰 테이블 (Sales)과 아주 작은 테이블 (Code_Master) 조인
large_df = spark.table(&quot;sales_data&quot;)
small_df = spark.table(&quot;item_codes&quot;)

# broadcast() 힌트를 사용하여 BHJ 유도
optimized_join = large_df.join(broadcast(small_df), &quot;item_id&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 효과적인 최적화입니다. 작은 테이블을 모든 Executor에 복제하여 셔플(Shuffle) 없이 조인을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부동작은&amp;nbsp; small_df를 Drive가 수집을하게되고 Executor들로 모든 워커노드에 small_df를 복사하게됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Executort에서 localjoin을 수행하게 됩니다. 그렇게 되면 large_df는 분산 상태 그대로 유지가 되고 small_df는 각 노드에 있게됩니다 Network가 최소화되고 속도가 매우 빠릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;적용 대상:&lt;/b&gt; 한쪽 테이블이 메모리에 올릴 만큼 충분히 작을 때 (기본값 10MB, 설정으로 조정 가능).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;효과:&lt;/b&gt; 네트워크를 통한 데이터 재배치 과정이 생략되어 속도가 획기적으로 빨라집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Sort Merge Join(SMJ)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 195207.png&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ykLOU/dJMcadnUceM/K46IneFAqyZ9vTLaS0gvTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ykLOU/dJMcadnUceM/K46IneFAqyZ9vTLaS0gvTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ykLOU/dJMcadnUceM/K46IneFAqyZ9vTLaS0gvTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FykLOU%2FdJMcadnUceM%2FK46IneFAqyZ9vTLaS0gvTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1202&quot; height=&quot;580&quot; data-filename=&quot;화면 캡처 2026-03-30 195207.png&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;join은 shuffle을 하여 리소스를 많이 잡아 먹는 연산입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워커가 3개가있는 클러스터에 데이터가 분산되어있으면 join을 하기위해선 두테이블의 키가 같아야 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두키를 하나의 워커노드로 몰아주는 셔플이 일어나게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로는 셔플이 일어나고 정렬까지 일어난다음에 join이 발생하는 연산입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 그림처럼 각 색깔별로 모아지는 과정이 먼저 발생을하게됩니다. 그다음 모아졌다면 sorting하는 과정이 생깁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 195628.png&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG9aqj/dJMcajn466L/S2dD6WfxKs1Og9yJdUpU5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG9aqj/dJMcajn466L/S2dD6WfxKs1Og9yJdUpU5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG9aqj/dJMcajn466L/S2dD6WfxKs1Og9yJdUpU5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG9aqj%2FdJMcajn466L%2FS2dD6WfxKs1Og9yJdUpU5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1136&quot; height=&quot;374&quot; data-filename=&quot;화면 캡처 2026-03-30 195628.png&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 195738.png&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNiTFB/dJMcai3Nhwk/1LWfTNPMS3kymbWtKBO540/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNiTFB/dJMcai3Nhwk/1LWfTNPMS3kymbWtKBO540/img.png&quot; data-alt=&quot;sortmergejoin을 할수있도록 설정해준 모습입니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNiTFB/dJMcai3Nhwk/1LWfTNPMS3kymbWtKBO540/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNiTFB%2FdJMcai3Nhwk%2F1LWfTNPMS3kymbWtKBO540%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;844&quot; height=&quot;325&quot; data-filename=&quot;화면 캡처 2026-03-30 195738.png&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;sortmergejoin을 할수있도록 설정해준 모습입니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습으로 만들었던 fact테이블과 디멘션 테이블을 가지고 join을 하였고 id기반으로 inner join을 했습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업에 대해서 explain을 던져서 실행계획을 보는거를 찍어 보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 195950.png&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNIdpG/dJMcabp9fo2/9kPKJlIGaxQYDFW1wDCPb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNIdpG/dJMcabp9fo2/9kPKJlIGaxQYDFW1wDCPb0/img.png&quot; data-alt=&quot;scan = 읽기 Exchange = 셔플 sort = 정렬이 일어난걸 확인할수가있는데 이걸 sortmergejoin이라고 할수있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNIdpG/dJMcabp9fo2/9kPKJlIGaxQYDFW1wDCPb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNIdpG%2FdJMcabp9fo2%2F9kPKJlIGaxQYDFW1wDCPb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1542&quot; height=&quot;443&quot; data-filename=&quot;화면 캡처 2026-03-30 195950.png&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;scan = 읽기 Exchange = 셔플 sort = 정렬이 일어난걸 확인할수가있는데 이걸 sortmergejoin이라고 할수있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774682090152&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 환경 설정 (명시적으로 SMJ를 선호하도록 설정)
spark.conf.set(&quot;spark.sql.join.preferSortMergeJoin&quot;, &quot;true&quot;)
spark.conf.set(&quot;spark.sql.autoBroadcastJoinThreshold&quot;, &quot;-1&quot;) # 브로드캐스트 방지 (테스트용)

# 2. 대용량 데이터프레임 생성 (예시)
large_df_1 = spark.table(&quot;huge_sales_data&quot;)
large_df_2 = spark.table(&quot;huge_customer_data&quot;)

# 3. Join 실행
# 별도의 힌트 없이도 대용량 vs 대용량이면 SMJ가 발생함
smj_result = large_df_1.join(large_df_2, &quot;customer_id&quot;, &quot;inner&quot;)

# 4. 실행 계획 확인 (Physical Plan에서 SortMergeJoin 확인)
smj_result.explain()&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;explain() 출력 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1774682111084&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;== Physical Plan ==
*(3) SortMergeJoin [customer_id#10], [customer_id#50], Inner
:- *(1) Sort [customer_id#10 ASC NULLS FIRST], false, 0
:  +- Exchange hashpartitioning(customer_id#10, 200)
+- *(2) Sort [customer_id#50 ASC NULLS FIRST], false, 0
   +- Exchange hashpartitioning(customer_id#50, 200)&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;Exchange: Shuffle 단계를 의미합니다.&lt;/li&gt;
&lt;li&gt;Sort: Merge 전 정렬 단계를 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Sort Merge Join 최적화 포인트 (Bucketing)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SMJ의 가장 큰 비용은 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;14&quot;&gt;Shuffle&lt;/b&gt;과 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;14&quot;&gt;Sort&lt;/b&gt;입니다. 만약 동일한 키로 조인을 자주 한다면, 데이터를 저장할 때 미리 &lt;b data-index-in-node=&quot;68&quot; data-path-to-node=&quot;14&quot;&gt;Bucketing&lt;/b&gt;을 해두어 이 비용을 제거할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-path-to-node=&quot;14&quot; data-index-in-node=&quot;68&quot;&gt;Bucketing = Shuffle 없이 같은 키 데이터를 같은 파티션에 미리 정렬해서 저장&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-path-to-node=&quot;14&quot; data-index-in-node=&quot;68&quot;&gt;아래 코드와 같이 같은 bucket key일때만 최적화 조건이 적용됩니다. 조건이 충족되면 바로 merge처리가 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774682187927&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 데이터를 저장할 때 미리 조인 키로 버켓팅하여 저장
large_df_1.write \
    .bucketBy(100, &quot;customer_id&quot;) \
    .sortBy(&quot;customer_id&quot;) \
    .saveAsTable(&quot;bucketed_sales&quot;)

large_df_2.write \
    .bucketBy(100, &quot;customer_id&quot;) \
    .sortBy(&quot;customer_id&quot;) \
    .saveAsTable(&quot;bucketed_customers&quot;)

# 이후 조인 시 Shuffle과 Sort가 생략됨 (성능 극대화)
df1 = spark.table(&quot;bucketed_sales&quot;)
df2 = spark.table(&quot;bucketed_customers&quot;)
optimized_smj = df1.join(df2, &quot;customer_id&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Sort Merge Join&lt;span&gt; 주요단계 분석&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 200138.png&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpIBBI/dJMcaaEL5ps/30MeF2uSTrvzvCyxBNo300/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpIBBI/dJMcaaEL5ps/30MeF2uSTrvzvCyxBNo300/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpIBBI/dJMcaaEL5ps/30MeF2uSTrvzvCyxBNo300/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpIBBI%2FdJMcaaEL5ps%2F30MeF2uSTrvzvCyxBNo300%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;849&quot; height=&quot;485&quot; data-filename=&quot;화면 캡처 2026-03-30 200138.png&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;BHJ VS SMJ&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;18&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Broadcast Hash Join (BHJ)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Sort Merge Join (SMJ)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0,0&quot;&gt;데이터 크기&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,1,1,0&quot;&gt;한쪽이 매우 작음 (기본 &amp;lt; 10MB)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,1,2,0&quot;&gt;둘 다 매우 큼&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,2,0,0&quot;&gt;셔플 여부&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,2,1,0&quot;&gt;없음 (네트워크 부하 적음)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,2,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,2,2,0&quot;&gt;있음&lt;/b&gt; (네트워크 부하 높음)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,3,0,0&quot;&gt;정렬 여부&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,3,1,0&quot;&gt;필요 없음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,3,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,3,2,0&quot;&gt;필수&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,4,0,0&quot;&gt;안정성&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,4,1,0&quot;&gt;메모리 초과(OOM) 위험 있음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,4,2,0&quot;&gt;매우 안정적 (Disk 사용 가능)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Narrow vs Wide Transformation 구분&lt;/h4&gt;
&lt;pre id=&quot;code_1774681970299&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Narrow Transformation (셔플 없음 - 빠름)
# 각 파티션 내에서 독립적으로 실행됨
df_narrow = df.filter(df.age &amp;gt; 20).select(&quot;name&quot;, &quot;age&quot;)

# Wide Transformation (셔플 발생 - 비용 높음)
# 데이터를 네트워크로 주고받아야 함
df_wide = df.groupBy(&quot;age&quot;).count()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 짤 때 내가 쓰는 함수가 셔플을 유발하는지 아는 것이 중요합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Window Function 최적화 (PartitionBy)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 함수(row_number, rank 등)사용 시 특정 키에 데이터가 몰리면 성능이 급격히 저하됩니다. 이때 윈도우 범위를 적절히 설정하는 것이 중요합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774681813961&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.window import Window
from pyspark.sql.functions import row_number

# 잘못된 예: 모든 데이터를 하나의 파티션으로 모으는 행위 (OOM 위험)
# window_spec = Window.orderBy(&quot;timestamp&quot;) 

# 권장 예: 반드시 partitionBy를 함께 사용하여 분산 처리 유도
window_spec = Window.partitionBy(&quot;user_id&quot;).orderBy(&quot;timestamp&quot;)

df.withColumn(&quot;rank&quot;, row_number().over(window_spec))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;user_id 기준으로 그룹 나눔&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;rarr; 각 그룹 안에서만 정렬 + row_number 수행&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;User Defined Functions (UDF) 지양&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python UDF는 JVM과 Python 프로세스 간의 데이터 직렬화/역직렬화 비용이 매우 큽니다. 가급적 &lt;b&gt;Built-in Functions(Spark SQL)&lt;/b&gt;를 사용하세요.&lt;/p&gt;
&lt;pre id=&quot;code_1774681848920&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 비효율적인 방식 (Python UDF)
from pyspark.sql.functions import udf
@udf(&quot;string&quot;)
def upper_udf(s):
    return s.upper() if s else None

# 효율적인 방식 (Spark Native Function) - 내부적으로 최적화됨
from pyspark.sql.functions import upper
df.withColumn(&quot;name_upper&quot;, upper(df[&quot;name&quot;]))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UDF를 가급적으로 피해야하는 이유는 Native가 왜빠른지에 대한코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python 코드를 사용하였을때는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ JVM &amp;rarr; Python 데이터 전달&lt;br /&gt;2️⃣ Python에서 실행&lt;br /&gt;3️⃣ 다시 JVM으로 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이과정에서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;422&quot; data-start=&quot;401&quot; data-section-id=&quot;1105hvu&quot;&gt;Serialization 비용 발생&lt;/li&gt;
&lt;li data-end=&quot;437&quot; data-start=&quot;423&quot; data-section-id=&quot;vleqi&quot;&gt;CPU 컨텍스트 스위칭&lt;/li&gt;
&lt;li data-end=&quot;450&quot; data-start=&quot;438&quot; data-section-id=&quot;12b76pe&quot;&gt;병렬 처리 효율 &amp;darr;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;동일한 로직이라도&lt;b&gt; Python UDF는 JVM과 Python 간 데이터 직렬화 비용과 Catalyst 최적화 비활성화로 인해 성능 저하가 발생하므로, 가능한 경우 Spark의 built-in&lt;/b&gt; 함수를 사용하는 것이 바람직합니다.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;수십만 건 &amp;rarr; 차이 적음&lt;/span&gt;&lt;br /&gt;&lt;span&gt;수억 건 &amp;rarr; 몇 배 이상 차이&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DAG 및 Stage 구조 확인을 위한 Spark UI 활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 짠 후에는 반드시 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;21&quot;&gt;Spark UI&lt;/b&gt;의 &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;21&quot;&gt;SQL 탭&lt;/b&gt;에서 DAG(Directed Acyclic Graph)를 확인해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774682013149&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 실행 계획(Physical Plan) 출력
df.explain()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;Exchange&lt;/b&gt;: 셔플이 발생하고 있음을 의미합니다. 이 부분이 너무 많다면 Join이나 GroupBy 전략을 다시 점검해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0&quot;&gt;WholeStageCodegen&lt;/b&gt;: 여러 연산이 하나의 단계로 합쳐져 최적화되었는지 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Data Layer (I/O 최적화)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark 성능 튜닝의 두 번째 단계인 &lt;b&gt;Data Layer (I/O 최적화)는&lt;/b&gt; 데이터를 읽고 쓰는 과정에서 발생하는 &lt;b&gt;병목을 제거하는 것이&lt;/b&gt; 핵심입니다.&lt;b&gt; &quot;필요한 데이터만, 가장 효율적인 크기로 읽는다&quot;&lt;/b&gt;는 원칙을 실현하는 구체적인 코드입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Format (Parquet) &amp;amp; Column Pruning (동적 파티션 프루닝)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 프루닝이 한글로 번역을 하면 가지치기라는 뜻이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 213450.png&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;605&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P0nB0/dJMcagdUETC/gMWorKxUB3URjiQPao87E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P0nB0/dJMcagdUETC/gMWorKxUB3URjiQPao87E0/img.png&quot; data-alt=&quot;그러니까 필요없는 것들는 어떤것들을 쳐낸다는 뜻이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P0nB0/dJMcagdUETC/gMWorKxUB3URjiQPao87E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP0nB0%2FdJMcagdUETC%2FgMWorKxUB3URjiQPao87E0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1064&quot; height=&quot;605&quot; data-filename=&quot;화면 캡처 2026-03-30 213450.png&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;605&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그러니까 필요없는 것들는 어떤것들을 쳐낸다는 뜻이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀스캔 하라고 명령을 던져도 내부적으로 이것들은 스캔할 필요가 없다고 판단이 되어지면 그런것들은 따로 스캔하지 않고 진짜 필요한것들만 스캔을해서 그만큼 작업의 효율을 높이는 최적화 방식입니다. 이것도 이해가안되면 정말 쉽게 설명하자면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 213655.png&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pj1nj/dJMcacCz97a/7qngtjQhHBk4qlHR2DvP31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pj1nj/dJMcacCz97a/7qngtjQhHBk4qlHR2DvP31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pj1nj/dJMcacCz97a/7qngtjQhHBk4qlHR2DvP31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPj1nj%2FdJMcacCz97a%2F7qngtjQhHBk4qlHR2DvP31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1028&quot; height=&quot;480&quot; data-filename=&quot;화면 캡처 2026-03-30 213655.png&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활성전에는 풀스캔때리면서 A부터 C까지 전체스캔을 통하지만 활성화가 되어있으면 A랑 C만 체크하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 213825.png&quot; data-origin-width=&quot;1391&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wpRdY/dJMcahcNB3C/wJ2OnGVH3ycihy27cQz7y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wpRdY/dJMcahcNB3C/wJ2OnGVH3ycihy27cQz7y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wpRdY/dJMcahcNB3C/wJ2OnGVH3ycihy27cQz7y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwpRdY%2FdJMcahcNB3C%2FwJ2OnGVH3ycihy27cQz7y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1391&quot; height=&quot;403&quot; data-filename=&quot;화면 캡처 2026-03-30 213825.png&quot; data-origin-width=&quot;1391&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업이 진행되는동안 자동적으로 제거를 해주게됩니다&lt;b&gt;(런타임 필터링!)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제가 만약쿼리를 던져서 1번인 Electronics를 filter를 찾는 쿼리를 던졌다고 가정해보고 AQE 전과 후의 차이를 보여드리도록 하겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 214130.png&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2cqmn/dJMcahqlT9B/Ah4V3zQmpgKkxhs5cYkZNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2cqmn/dJMcahqlT9B/Ah4V3zQmpgKkxhs5cYkZNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2cqmn/dJMcahqlT9B/Ah4V3zQmpgKkxhs5cYkZNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2cqmn%2FdJMcahqlT9B%2FAh4V3zQmpgKkxhs5cYkZNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;323&quot; height=&quot;162&quot; data-filename=&quot;화면 캡처 2026-03-30 214130.png&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 214114.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FZ4fY/dJMcac3BWbH/76JfPsKiUm8PMPrJsAdPoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FZ4fY/dJMcac3BWbH/76JfPsKiUm8PMPrJsAdPoK/img.png&quot; data-alt=&quot;Query를 던졌을때 AQE가 비활성화가 되면 1번부터 3번까지 전부 다 스캔하게 됩니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FZ4fY/dJMcac3BWbH/76JfPsKiUm8PMPrJsAdPoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFZ4fY%2FdJMcac3BWbH%2F76JfPsKiUm8PMPrJsAdPoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1068&quot; height=&quot;246&quot; data-filename=&quot;화면 캡처 2026-03-30 214114.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Query를 던졌을때 AQE가 비활성화가 되면 1번부터 3번까지 전부 다 스캔하게 됩니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 214034.png&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvn2Cq/dJMb99Mz6kD/2upYkRlb52TngSraqhG8cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvn2Cq/dJMb99Mz6kD/2upYkRlb52TngSraqhG8cK/img.png&quot; data-alt=&quot;AQE가 활성화가되면 일렉트로닉 1번만 스캔하게 됩니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvn2Cq/dJMb99Mz6kD/2upYkRlb52TngSraqhG8cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbvn2Cq%2FdJMb99Mz6kD%2F2upYkRlb52TngSraqhG8cK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;995&quot; height=&quot;433&quot; data-filename=&quot;화면 캡처 2026-03-30 214034.png&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AQE가 활성화가되면 일렉트로닉 1번만 스캔하게 됩니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 214334.png&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQjWY8/dJMcagSvPP0/9ffMhjHcKvqGpNrley87ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQjWY8/dJMcagSvPP0/9ffMhjHcKvqGpNrley87ek/img.png&quot; data-alt=&quot;explain을 찍어보게되면 dynamicpruning이라고하는 결과값을 얻을수 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQjWY8/dJMcagSvPP0/9ffMhjHcKvqGpNrley87ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQjWY8%2FdJMcagSvPP0%2F9ffMhjHcKvqGpNrley87ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1310&quot; height=&quot;399&quot; data-filename=&quot;화면 캡처 2026-03-30 214334.png&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;399&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;explain을 찍어보게되면 dynamicpruning이라고하는 결과값을 얻을수 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSV나 JSON 같은 텍스트 포맷 대신 Parquet(열 지향 저장소)을 사용하면, 필요한 컬럼만 선택해서 읽는 &lt;b data-index-in-node=&quot;68&quot; data-path-to-node=&quot;3&quot;&gt;Column Pruning&lt;/b&gt;이 엔진 수준에서 자동으로 이루어집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774686513839&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. Parquet 형식으로 저장 (압축률과 I/O 효율이 가장 좋음)
df.write.mode(&quot;overwrite&quot;).parquet(&quot;data/sales_parquet&quot;)

# 2. Column Pruning 실습
# 전체 100개 컬럼 중 2개만 선택하면, Spark는 디스크에서 해당 컬럼의 블록만 읽어옵니다.
optimized_df = spark.read.parquet(&quot;data/sales_parquet&quot;) \
    .select(&quot;user_id&quot;, &quot;amount&quot;) 

optimized_df.explain() # 계획서에 'ReadSchema' 부분에 선택한 컬럼만 나타남&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Column Pruning&lt;/b&gt;: 필요한 컬럼만 읽고, 나머지는 아예 읽지 않는 최적화&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RujvI/dJMcafF2WF5/jqo60UWrCb1oaU4kqgr9Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RujvI/dJMcafF2WF5/jqo60UWrCb1oaU4kqgr9Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RujvI/dJMcafF2WF5/jqo60UWrCb1oaU4kqgr9Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRujvI%2FdJMcafF2WF5%2Fjqo60UWrCb1oaU4kqgr9Tk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;581&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;581&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Partitioning &amp;amp; Partition Pruning&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 물리적으로 디렉토리 구조(예: year=2023/month=10)로 나누어 저장하면, 조건에 맞는 디렉토리만 스캔하는 &lt;b data-index-in-node=&quot;71&quot; data-path-to-node=&quot;7&quot;&gt;Partition Pruning&lt;/b&gt;이 작동합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774686668947&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 특정 컬럼 기준으로 파티셔닝하여 저장
df.write.partitionBy(&quot;event_date&quot;, &quot;region&quot;).parquet(&quot;data/events&quot;)

# 2. Partition Pruning 실습
# 이 쿼리는 전체 데이터를 뒤지는 것이 아니라 'event_date=2023-10-01' 폴더만 스캔합니다.
filtered_df = spark.read.parquet(&quot;data/events&quot;) \
    .filter(&quot;event_date = '2023-10-01' AND region = 'KR'&quot;)

filtered_df.explain() # 'PartitionFilters' 항목에 조건이 포함된 것을 확인 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Partition Pruning&lt;/b&gt; : 필요한 파티션(파일/디렉토리)만 읽고, 나머지는 아예 스킵하는 최적화&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;655&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnWBH3/dJMcagSust1/Kg9yRUUn4YcsKoXKNBPffk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnWBH3/dJMcagSust1/Kg9yRUUn4YcsKoXKNBPffk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnWBH3/dJMcagSust1/Kg9yRUUn4YcsKoXKNBPffk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnWBH3%2FdJMcagSust1%2FKg9yRUUn4YcsKoXKNBPffk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;655&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;655&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Predicate Pushdown&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필터 조건(filter, where)을 데이터 소스(저장소) 레벨로 내려보내, 메모리로 올리기 전에 데이터를 걸러내는 기술입니다. Parquet/ORC 포맷에서 가장 잘 작동합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774686796371&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Spark은 filter 조건을 분석하여 최대한 저장소 단에서 걸러내도록 명령합니다.
# 아래 코드 실행 시 실제 'amount &amp;gt; 1000' 조건은 스캔 단계에서 적용됩니다.
df = spark.read.parquet(&quot;data/sales&quot;) \
    .filter(df.amount &amp;gt; 1000)

df.explain() # 'PushedFilters' 항목에 [GreaterThan(amount, 1000)] 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필터 조건을 최대한 데이터 소스까지 내려보내서, 읽을 데이터 자체를 줄이는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PushedFilters에 뜨면 &amp;rarr; 실제로 데이터 읽기 전에 필터 적용된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parquet은 컬럼기반저장 + 통계 존재하기때문에 이파일에서 amount &amp;gt; 1000이 있는지 메타데이터로 판단 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;Parquet 데이터 소스를 사용할 경우 Spark는 filter 조건을 분석하여 PushedFilters 형태로 &lt;b&gt;데이터 스캔 단계에 적용&lt;/b&gt;하며, 이를 통해 불필요한 데이터를 읽지 않아 I/O 비용을 크게 줄일 수 있습니다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Small File 문제 해결 (File Size 최적화)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 크기가 너무 작으면(수 KB 단위 등) 메타데이터 관리 부하가 커지고 스캔 속도가 느려집니다. 이를 방지하기 위해 저장 시 적절한 크기로 합쳐줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774686829474&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 저장 시 Coalesce를 사용하여 파일 개수 줄이기
# 파티션당 약 128MB ~ 256MB가 되도록 설정하는 것이 이상적입니다.
df.coalesce(5).write.parquet(&quot;data/optimized_files&quot;)

# 2. Spark 3.x AQE를 이용한 읽기 시 자동 병합
spark.conf.set(&quot;spark.sql.adaptive.enabled&quot;, &quot;true&quot;)
spark.conf.set(&quot;spark.sql.adaptive.coalescePartitions.enabled&quot;, &quot;true&quot;)

# 3. 이미 작은 파일이 많은 경우 (Compaction 작업)
# 작은 파일들이 가득한 경로를 읽어서 다시 적절한 수의 파티션으로 덮어쓰기
spark.read.parquet(&quot;data/small_files_dir&quot;) \
    .repartition(10) \
    .write.mode(&quot;overwrite&quot;).parquet(&quot;data/compacted_files&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 데이터 이미 균등하게 분산되어 있음 + 파일 개수만 줄이고 싶을 때 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Small File 문제 = &quot;파일이 너무 많아서 처리 오버헤드가 폭발하는 문제&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Small File 해결 = &quot;쓰기 때 줄이고 + 읽을 때 자동 최적화 + 이미 쌓인 건 재정리&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;Small File 문제는 과도한 파일 수로 인해 I/O와 Task 스케줄링 오버헤드가 증가하는 문제이며, 이를 해결하기 위해 쓰기 시 repartition 또는 coalesce로 파일 수를 줄이고, AQE를 활용해 실행 시 파티션을 최적화하며, 이미 생성된 작은 파일은 compaction을 통해 재정리합니다.&amp;rdquo;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;I/O 관련 주요 설정 (Tips)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 레이어 최적화를 위해 코드 외에 설정값으로 조절할 수 있는 부분들입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774686879643&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spark.conf.set(&quot;spark.sql.adaptive.enabled&quot;, &quot;true&quot;)
spark.conf.set(&quot;spark.sql.adaptive.coalescePartitions.enabled&quot;, &quot;true&quot;)

spark.conf.set(&quot;spark.sql.shuffle.partitions&quot;, 200)
spark.conf.set(&quot;spark.sql.files.maxPartitionBytes&quot;, 134217728)

spark.conf.set(&quot;spark.sql.files.openCostInBytes&quot;, 4194304)
spark.conf.set(&quot;spark.sql.parquet.filterPushdown&quot;, &quot;true&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 설정 요약&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2286&quot; data-start=&quot;2040&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;설정&lt;/td&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2100&quot; data-start=&quot;2068&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2088&quot; data-start=&quot;2068&quot;&gt;maxPartitionBytes&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2100&quot; data-start=&quot;2088&quot;&gt;읽기 단위 크기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2134&quot; data-start=&quot;2101&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2122&quot; data-start=&quot;2101&quot;&gt;shuffle.partitions&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2134&quot; data-start=&quot;2122&quot;&gt;병렬 처리 개수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2167&quot; data-start=&quot;2135&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2153&quot; data-start=&quot;2135&quot;&gt;openCostInBytes&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2167&quot; data-start=&quot;2153&quot;&gt;파일 open 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2186&quot; data-start=&quot;2168&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2174&quot; data-start=&quot;2168&quot;&gt;AQE&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2186&quot; data-start=&quot;2174&quot;&gt;실행 중 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2221&quot; data-start=&quot;2187&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2208&quot; data-start=&quot;2187&quot;&gt;coalescePartitions&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2221&quot; data-start=&quot;2208&quot;&gt;파티션 자동 병합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2254&quot; data-start=&quot;2222&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2242&quot; data-start=&quot;2222&quot;&gt;maxRecordsPerFile&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2254&quot; data-start=&quot;2242&quot;&gt;파일 크기 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2286&quot; data-start=&quot;2255&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2272&quot; data-start=&quot;2255&quot;&gt;filterPushdown&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2286&quot; data-start=&quot;2272&quot;&gt;읽기 데이터 최소화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. Execution Layer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark 성능 튜닝의 세 번째 단계인 &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;0&quot;&gt;Execution Layer&lt;/b&gt;는 데이터가 메모리에 적재된 후의 &lt;b data-index-in-node=&quot;56&quot; data-path-to-node=&quot;0&quot;&gt;실행 효율&lt;/b&gt;을 다룹니다. 연산 결과를 어떻게 재사용하고, 한정된 메모리를 어떻게 나누어 쓰며, 디스크 스필(Spill)을 어떻게 방지할지가 핵심입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cache &amp;amp; Persist (데이터 재사용)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 DataFrame을 여러 액션(Action)에서 반복 사용할 때 메모리에 올려두는 전략입니다&lt;/p&gt;
&lt;pre id=&quot;code_1774687107320&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark import StorageLevel

# 1. cache(): 메모리에만 저장 (MEMORY_AND_DISK가 기본인 경우도 있음)
df = spark.read.parquet(&quot;large_data&quot;).filter(&quot;amount &amp;gt; 1000&quot;)
df.cache() 

# 2. persist(): 저장 수준을 세밀하게 제어
# MEMORY_AND_DISK_SER: 메모리가 부족하면 디스크에 쓰고, 직렬화하여 용량 아낌
df_persisted = df.persist(StorageLevel.MEMORY_AND_DISK_SER)

# 3. 사용 후 반드시 해제 (메모리 확보)
df.unpersist()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cache()는 만능이 아닙니다. 단 한 번만 사용하는 DataFrame에 사용하면 오히려 캐싱을 위한 오버헤드(직렬화, 메모리 점유) 때문에 더 느려집니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Memory Tuning (메모리 구조 이해)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;Executor 메모리는 크게 두 영역으로 나뉩니다. 이 비율을 조정하여 성능을 최적화할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;Execution Memory:&lt;/b&gt; Join, Shuffle, Sort 연산 시 사용&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;Storage Memory:&lt;/b&gt; cache(), persist() 시 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774687193357&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 전체 메모리 중 Spark이 사용할 비율 (기본 0.6)
spark.conf.set(&quot;spark.memory.fraction&quot;, &quot;0.8&quot;)

# Spark 메모리 중 Storage가 차지할 비율 (기본 0.5)
# 캐시를 많이 안 쓴다면 이 값을 낮춰서 Execution 영역을 확보하세요.
spark.conf.set(&quot;spark.memory.storageFraction&quot;, &quot;0.3&quot;)

# Off-heap 메모리 사용 (JVM 밖의 메모리 사용으로 GC 부하 감소)
spark.conf.set(&quot;spark.memory.offHeap.enabled&quot;, &quot;true&quot;)
spark.conf.set(&quot;spark.memory.offHeap.size&quot;, &quot;2g&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Spill 방지 (Disk Spill 제거)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spill = &quot;한 파티션 데이터가 너무 큼&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15&quot;&gt;Spill&lt;/b&gt;은 Execution 메모리가 부족하여 데이터를 디스크에 임시로 쓰는 현상입니다. 셔플이나 조인이 급격히 느려지는 주범입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774687392806&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spark.conf.set(&quot;spark.sql.shuffle.partitions&quot;, &quot;400&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Spill 확인 및 해결 방법&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;Spill 발생 &amp;rarr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;1. Partition 충분한가?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;2. Skew 있는가?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;3. Broadcast 가능?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;4. Memory 부족?&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;Spark UI 확인:&lt;/b&gt; Stage 상세 페이지에서 &lt;b data-index-in-node=&quot;28&quot; data-path-to-node=&quot;17,0,0&quot;&gt;&quot;Shuffle Spill (Disk)&quot;&lt;/b&gt; 항목이 0보다 크다면 문제가 있는 것입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;해결 코드:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파티션 수를 늘려 개별 태스크가 처리하는 데이터양을 줄입니다.&lt;/li&gt;
&lt;li&gt;spark.executor.memory 값을 높입니다.&lt;/li&gt;
&lt;li&gt;메모리 내 연산 효율을 높이기 위해 spark.sql.shuffle.partitions를 조절합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spill 방지 = &quot;데이터를 잘게 나누고, 메모리를 충분히 확보&quot;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GC(Garbage Collection) 최적화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC란 = &quot;더 이상 사용하지 않는 메모리를 자동으로 정리하는 것&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark은 JVM 위에서 돌아가므로 객체가 많아지면 GC 오버헤드가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;보이지 않지만 성능을 크게 깎아먹는 영역&amp;rdquo; = GC 최적화&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774687445472&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Kryo Serializer 설정 (기본보다 빠르고 작음)
spark.conf.set(&quot;spark.serializer&quot;, &quot;org.apache.spark.serializer.KryoSerializer&quot;)

# 런타임 시 G1GC 사용 설정 (spark-submit 시 적용)
# --conf &quot;spark.executor.extraJavaOptions=-XX:+UseG1GC&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;해결책:&lt;/b&gt; * &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;22,0,0&quot;&gt;직렬화(Serialization):&lt;/b&gt; Kryo Serializer를 사용하여 객체 크기를 줄입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,1,0,0&quot;&gt;G1GC 사용:&lt;/b&gt; 최신 JVM에서는 G1GC가 대용량 메모리 처리에 유리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GC 종류 비교&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1342&quot; data-start=&quot;1246&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GC&lt;/td&gt;
&lt;td&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1301&quot; data-start=&quot;1272&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1285&quot; data-start=&quot;1272&quot;&gt;ParallelGC&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1301&quot; data-start=&quot;1285&quot;&gt;빠르지만 pause 큼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1322&quot; data-start=&quot;1302&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1308&quot; data-start=&quot;1302&quot;&gt;CMS&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1322&quot; data-start=&quot;1308&quot;&gt;deprecated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1342&quot; data-start=&quot;1323&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1330&quot; data-start=&quot;1323&quot;&gt;G1GC&lt;/td&gt;
&lt;td data-end=&quot;1342&quot; data-start=&quot;1330&quot; data-col-size=&quot;sm&quot;&gt;균형형 (추천)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiqxMnGg8KTAxUAAAAAHQAAAAAQ4gE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;핵심 요약 테이블&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;26&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;최적화 항목&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;권장 전략&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;비고&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,1,0,0&quot;&gt;Caching&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,1,1,0&quot;&gt;MEMORY_AND_DISK_SER 추천&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,1,2,0&quot;&gt;직렬화로 메모리 효율 증대&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,2,0,0&quot;&gt;Spill 제거&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,2,1,0&quot;&gt;파티션 개수 증가&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,2,2,0&quot;&gt;Spark UI 모니터링 필수&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,3,0,0&quot;&gt;Off-heap&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,3,1,0&quot;&gt;대용량 Join 시 활성화&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,3,2,0&quot;&gt;GC 부하 감소 효과&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,4,0,0&quot;&gt;Memory Ratio&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,4,1,0&quot;&gt;캐시 적으면 storageFraction 하향&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;26,4,2,0&quot;&gt;연산 메모리(Execution) 확보&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4.Skew Handling(데이터 불균형 제거)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;1&quot;&gt;Data Skew&lt;/b&gt;란 특정 키에 데이터가 몰려, 100개의 태스크 중 99개는 1초 만에 끝나는데 &lt;b data-index-in-node=&quot;54&quot; data-path-to-node=&quot;1&quot;&gt;단 1개의 태스크만 30분 동안 돌아가는 현상&lt;/b&gt;을 말합니다. 이를 해결하는 구체적인 전략과 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Skew Detection (현상 파악)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;코드를 짜기 전, &lt;b data-index-in-node=&quot;10&quot; data-path-to-node=&quot;4&quot;&gt;Spark UI&lt;/b&gt;를 통해 Skew를 확인하는 것이 우선입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;Stage 탭:&lt;/b&gt; Tasks 목록에서 Max 실행 시간이 Median 실행 시간보다 압도적으로 길고, 특정 Executor의 Shuffle Read Size가 유독 크다면 100% Skew입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Salting 기법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Salting =&lt;b&gt; &quot;쏠린 데이터를 인위적으로 쪼개서 여러 노드로 분산시키는 기술&quot; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Salting은 Data Skew를 해결하기 위해 특정 key에 랜덤 값을 추가하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 Partition으로 분산시키는 기법으로, 특히 Join 연산에서 성능 개선에 효과적입니다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;user_id = A &amp;rarr; 90%&lt;br /&gt;user_id = B,C,D &amp;rarr; 10%&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 조인 키에 임의의 숫자(Salt)를 붙여 데이터를 강제로 분산시킨 뒤 조인하는 방식입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774687632472&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql import functions as F
import random

# 1. Skew가 발생한 테이블 (예: 대용량 로그)에 Salt 추가
# 0~9 사이의 랜덤 숫자를 키 뒤에 붙임
skewed_df = spark.table(&quot;large_log_data&quot;)
salted_df = skewed_df.withColumn(&quot;salt&quot;, (F.rand() * 10).cast(&quot;int&quot;)) \
                     .withColumn(&quot;salted_key&quot;, F.concat(F.col(&quot;user_id&quot;), F.lit(&quot;_&quot;), F.col(&quot;salt&quot;)))

# 2. 상대적으로 작은 테이블 (예: 유저 마스터)을 10배로 복제 (Explode)
# Salt 값 0~9에 모두 대응할 수 있도록 행을 늘림
small_df = spark.table(&quot;user_master&quot;)
salt_values = spark.range(10).withColumnRenamed(&quot;id&quot;, &quot;salt&quot;)

expanded_small_df = small_df.crossJoin(salt_values) \
                            .withColumn(&quot;salted_key&quot;, F.concat(F.col(&quot;user_id&quot;), F.lit(&quot;_&quot;), F.col(&quot;salt&quot;)))

# 3. Salted Key로 Join 실행 (데이터가 분산되어 병목 제거)
result = salted_df.join(expanded_small_df, &quot;salted_key&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AQE Skew Join Optimization (자동화)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;Spark 3.0 이상이라면 복잡한 Salting 코드 대신 &lt;b data-index-in-node=&quot;34&quot; data-path-to-node=&quot;13&quot;&gt;AQE(Adaptive Query Execution)&lt;/b&gt; 기능을 켜서 해결할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;동작 원리:&lt;/b&gt; 실행 중에 Skew를 감지하면 Spark가 알아서 해당 파티션을 더 작은 단위로 쪼개어 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774687672622&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# AQE 활성화 및 Skew Join 최적화 옵션 켜기
spark.conf.set(&quot;spark.sql.adaptive.enabled&quot;, &quot;true&quot;)
spark.conf.set(&quot;spark.sql.adaptive.skewJoin.enabled&quot;, &quot;true&quot;)

# Skew라고 판단할 기준 설정 (기본값보다 낮게 잡으면 더 공격적으로 최적화)
spark.conf.set(&quot;spark.sql.adaptive.skewJoin.skewedPartitionFactor&quot;, &quot;5&quot;)
spark.conf.set(&quot;spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes&quot;, &quot;256mb&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Heavy Key 분리 처리 (Filtering)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 키(예: 'Guest' 유저나 'NULL' 값)가 전체의 80%를 차지하는 경우, 이들만 따로 떼어내서 처리하는 전략입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774687763213&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. Null이나 특정 Heavy Key 분리
heavy_key_df = df.filter(&quot;user_id IS NULL OR user_id = 'GUEST'&quot;)
normal_df = df.filter(&quot;user_id IS NOT NULL AND user_id != 'GUEST'&quot;)

# 2. Heavy Key는 별도 로직으로 처리 (예: Join 대신 단순 필터링 등)
# 3. 정상 데이터만 Join 수행 후 나중에 Union
result = normal_df.join(other_df, &quot;user_id&quot;).union(heavy_key_df_processed)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 요약 및 전략 선택&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;22&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;상황&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;추천 전략&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0,0&quot;&gt;일반적인 대용량 조인&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,1,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,1,0&quot;&gt;AQE Skew Join&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,1,2,0&quot;&gt;설정만으로 자동 해결되므로 1순위 고려&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,2,0,0&quot;&gt;AQE로 해결 안 되는 극심한 Skew&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,2,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,2,1,0&quot;&gt;Salting&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,2,2,0&quot;&gt;코드는 복잡하나 가장 확실하게 데이터를 분산함&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,3,0,0&quot;&gt;특정 키(NULL 등)가 원인일 때&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,3,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,3,1,0&quot;&gt;Filter &amp;amp; Union&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,3,2,0&quot;&gt;불필요한 연산을 원천 차단&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,4,0,0&quot;&gt;작은 테이블과의 조인 시&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,4,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,4,1,0&quot;&gt;Broadcast Join&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;22,4,2,0&quot;&gt;셔플 자체가 발생 안 하므로 Skew 영향 없음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Runtime Optimization (AQE: Adaptive Query Execution)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 210611.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJKlAP/dJMcaco2kF1/DAaQ9jlwql9Sb4yzKckGv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJKlAP/dJMcaco2kF1/DAaQ9jlwql9Sb4yzKckGv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJKlAP/dJMcaco2kF1/DAaQ9jlwql9Sb4yzKckGv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJKlAP%2FdJMcaco2kF1%2FDAaQ9jlwql9Sb4yzKckGv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1494&quot; height=&quot;547&quot; data-filename=&quot;화면 캡처 2026-03-30 210611.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;기존의 Spark은 실행 전(Compile Time)에 수립한 실행 계획을 끝까지 밀고 나갔습니다. 하지만 &lt;b data-index-in-node=&quot;60&quot; data-path-to-node=&quot;1&quot;&gt;AQE&lt;/b&gt;는 실행 중(Runtime)에 수집된 통계 정보를 바탕으로 &lt;b data-index-in-node=&quot;96&quot; data-path-to-node=&quot;1&quot;&gt;실행 계획을 동적으로 변경&lt;/b&gt;합니다. &quot;일단 해보고, 데이터가 생각보다 작으면 전략을 바꾸자!&quot;는 똑똑한 방식입니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 210835.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qbK7U/dJMcacWQZ2e/7JU8TdDaAcfWjnKgKgmN3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qbK7U/dJMcacWQZ2e/7JU8TdDaAcfWjnKgKgmN3K/img.png&quot; data-alt=&quot;AQE의 주요기능 4가지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qbK7U/dJMcacWQZ2e/7JU8TdDaAcfWjnKgKgmN3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqbK7U%2FdJMcacWQZ2e%2F7JU8TdDaAcfWjnKgKgmN3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;277&quot; data-filename=&quot;화면 캡처 2026-03-30 210835.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AQE의 주요기능 4가지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;1&quot; data-ke-size=&quot;size20&quot;&gt;AQE 활성화 (기본 설정)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AQE를 사용하려면 먼저 기능을 켜야 합니다. (Spark 3.2+ 부터는 기본값이 true이지만, 명시적으로 관리하는 것이 좋습니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1774687868898&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# AQE 전체 활성화
spark.conf.set(&quot;spark.sql.adaptive.enabled&quot;, &quot;true&quot;)

# Runtime 최적화를 위한 최소/최대 파티션 크기 설정
spark.conf.set(&quot;spark.sql.adaptive.advisoryPartitionSizeInBytes&quot;, &quot;128mb&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 210950.png&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5qqEl/dJMcad2uvfZ/K4MoZ5MLki9Vl5Obom3umK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5qqEl/dJMcad2uvfZ/K4MoZ5MLki9Vl5Obom3umK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5qqEl/dJMcad2uvfZ/K4MoZ5MLki9Vl5Obom3umK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5qqEl%2FdJMcad2uvfZ%2FK4MoZ5MLki9Vl5Obom3umK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;160&quot; data-filename=&quot;화면 캡처 2026-03-30 210950.png&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-path-to-node=&quot;1&quot; data-ke-size=&quot;size20&quot;&gt;Coalescing Post-shuffle Partitions&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 211049.png&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yP37Z/dJMcabcy70V/LZ5h3qebgF2RIYhXhgjCOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yP37Z/dJMcabcy70V/LZ5h3qebgF2RIYhXhgjCOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yP37Z/dJMcabcy70V/LZ5h3qebgF2RIYhXhgjCOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyP37Z%2FdJMcabcy70V%2FLZ5h3qebgF2RIYhXhgjCOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1104&quot; height=&quot;456&quot; data-filename=&quot;화면 캡처 2026-03-30 211049.png&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&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;하지만 자잘한 파티션들이 많이 생기게되면 스파크의 성능이 떨어지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐면 파티션 하나당 Task가 하나씩 할당이 되게 되는데, 자잘한 파티션들이 많아지면 Task가 많아져서 Spark작업에 부하가 걸릴수도 있기 때문입니다. 그래서 &lt;b&gt;AQE는 자잘하게 나눠진 파티션들을 하나의 파티션으로 합쳐주는것입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셔플 이후에 데이터가 급격히 작아진 파티션들을 &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;9&quot;&gt;자동으로 병합&lt;/b&gt;하여 태스크 개수를 줄여줍니다. (작은 파일 문제와 스케줄링 오버헤드 해결)&lt;/p&gt;
&lt;pre id=&quot;code_1774687921207&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 셔플 후 파티션 자동 병합 활성화
spark.conf.set(&quot;spark.sql.adaptive.coalescePartitions.enabled&quot;, &quot;true&quot;)

# 병합 후 최소 파티션 개수 지정
spark.conf.set(&quot;spark.sql.adaptive.coalescePartitions.minPartitionNum&quot;, &quot;1&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;상황:&lt;/b&gt; filter 연산 후 데이터가 90% 사라졌는데, 파티션은 여전히 200개일 때.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;효과:&lt;/b&gt; 불필요한 I/O와 태스크 생성 비용을 아낍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 211509.png&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMyQs6/dJMcagSvPlB/Jfp6kILWlTi8ZkgRfT2Au0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMyQs6/dJMcagSvPlB/Jfp6kILWlTi8ZkgRfT2Au0/img.png&quot; data-alt=&quot;AQE를 키고 결과값을 확인할때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMyQs6/dJMcagSvPlB/Jfp6kILWlTi8ZkgRfT2Au0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMyQs6%2FdJMcagSvPlB%2FJfp6kILWlTi8ZkgRfT2Au0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;182&quot; data-filename=&quot;화면 캡처 2026-03-30 211509.png&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AQE를 키고 결과값을 확인할때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Switching Join Strategies(효율적인 join방식 채택)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 212329.png&quot; data-origin-width=&quot;1259&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ej3LG/dJMcaiJvGP6/O9rYKV7N8RCVCKaZ8vzRDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ej3LG/dJMcaiJvGP6/O9rYKV7N8RCVCKaZ8vzRDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ej3LG/dJMcaiJvGP6/O9rYKV7N8RCVCKaZ8vzRDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEj3LG%2FdJMcaiJvGP6%2FO9rYKV7N8RCVCKaZ8vzRDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1259&quot; height=&quot;630&quot; data-filename=&quot;화면 캡처 2026-03-30 212329.png&quot; data-origin-width=&quot;1259&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 전에는 데이터 크기를 몰라 Sort Merge Join을 계획했더라도, 막상 데이터를 읽어보니 한쪽이 작다면 즉석에서 &lt;b data-index-in-node=&quot;69&quot; data-path-to-node=&quot;14&quot;&gt;Broadcast Hash Join으로 변경&lt;/b&gt;합니다. 이게 무슨말이냐면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Sort Merge Join 셔플과 정렬이 일어나기 때문에 비용이 많이 드는 조인 방법이지만 그만큼 안전한 방법입니다 브로드 케스트 조인은 엄청큰데이터와 작은데이터간의 조인입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;AQE를 활성화를 시키면 Join되려는 두테이블의 사이즈를 보고 Apache Spark가 판단해서 둘중하나로 변경해 효율적으로 바꿔주는 방법입니다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 213031.png&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eL9qz3/dJMcahDQVpk/gahl91SPkXrJqk6js47ZB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eL9qz3/dJMcahDQVpk/gahl91SPkXrJqk6js47ZB0/img.png&quot; data-alt=&quot;AQE를 비활성화 시킨상태에서 하나는 500만개 하나는 100개를 join하는 과정의 결과가 sortmergejoin이라고 나오는걸 알수가 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eL9qz3/dJMcahDQVpk/gahl91SPkXrJqk6js47ZB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeL9qz3%2FdJMcahDQVpk%2Fgahl91SPkXrJqk6js47ZB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;854&quot; height=&quot;630&quot; data-filename=&quot;화면 캡처 2026-03-30 213031.png&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AQE를 비활성화 시킨상태에서 하나는 500만개 하나는 100개를 join하는 과정의 결과가 sortmergejoin이라고 나오는걸 알수가 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 213135.png&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8Kkro/dJMb996UMyC/6EUc32ZJlDkwi0sLKKqCHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8Kkro/dJMb996UMyC/6EUc32ZJlDkwi0sLKKqCHK/img.png&quot; data-alt=&quot;이번에는 활성화를 시켜서 explain을 해본결과 broadcastjoin으로 변경된걸 볼수가 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8Kkro/dJMb996UMyC/6EUc32ZJlDkwi0sLKKqCHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8Kkro%2FdJMb996UMyC%2F6EUc32ZJlDkwi0sLKKqCHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;997&quot; height=&quot;389&quot; data-filename=&quot;화면 캡처 2026-03-30 213135.png&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;389&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이번에는 활성화를 시켜서 explain을 해본결과 broadcastjoin으로 변경된걸 볼수가 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774687953425&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 조인 전략 동적 변경 활성화
spark.conf.set(&quot;spark.sql.adaptive.localShuffleReader.enabled&quot;, &quot;true&quot;)

# 실행 계획 확인 시 'AQE' 문구가 포함된 것을 볼 수 있음
df_joined.explain()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;상황:&lt;/b&gt; 복잡한 필터링 결과 테이블이 10MB 이하로 줄어든 경우.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;효과:&lt;/b&gt; 무거운 셔플 과정을 통째로 생략합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Skew Join Optimization(편향데이터 분산)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 211633.png&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eqpVac/dJMcadalvXK/RqONuB3HmBwgo2WbnzxkLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eqpVac/dJMcadalvXK/RqONuB3HmBwgo2WbnzxkLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eqpVac/dJMcadalvXK/RqONuB3HmBwgo2WbnzxkLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeqpVac%2FdJMcadalvXK%2FRqONuB3HmBwgo2WbnzxkLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1182&quot; height=&quot;651&quot; data-filename=&quot;화면 캡처 2026-03-30 211633.png&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;651&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번의 key에 90%정도 몰려있다고 쳐보겠습니다 (A0 그림) 나머지는 이제 자잘하게 되어있는데, 1번파티션 하나만 너무 크게 되어있을때입니다. 그렇게 되면 파티션에 Task가 하나씩 할당이 되게 되는데 그렇게되면 혼자 과도한 작업을 처리하게 되기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그걸 나눠서 작업하는 과정입니다. 최초에는 하나의 파티션으로 존재를했지만 Spark 자체적으로 최적화를 시키면서 봤을때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 과도하게 큰 용량의 데이터다라고 판단이 들어서 쪼개어가지고 병렬로 작업이 될수 있도록 만들어주는것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 211959.png&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/emLS3W/dJMcaaY2B54/VqZi9tRLzev0SsswDiFYg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/emLS3W/dJMcaaY2B54/VqZi9tRLzev0SsswDiFYg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/emLS3W/dJMcaaY2B54/VqZi9tRLzev0SsswDiFYg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FemLS3W%2FdJMcaaY2B54%2FVqZi9tRLzev0SsswDiFYg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;110&quot; data-filename=&quot;화면 캡처 2026-03-30 211959.png&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;110&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 212024.png&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJK7Pf/dJMcacbuUR5/NMT1QNVVBqRhceJSEO58jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJK7Pf/dJMcacbuUR5/NMT1QNVVBqRhceJSEO58jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJK7Pf/dJMcacbuUR5/NMT1QNVVBqRhceJSEO58jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJK7Pf%2FdJMcacbuUR5%2FNMT1QNVVBqRhceJSEO58jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;855&quot; height=&quot;441&quot; data-filename=&quot;화면 캡처 2026-03-30 212024.png&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 212040.png&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pebrt/dJMcaaEL7u2/sCIHvzxIoB9xHGSjqRXsW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pebrt/dJMcaaEL7u2/sCIHvzxIoB9xHGSjqRXsW1/img.png&quot; data-alt=&quot;과도하게 분포되어있는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pebrt/dJMcaaEL7u2/sCIHvzxIoB9xHGSjqRXsW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPebrt%2FdJMcaaEL7u2%2FsCIHvzxIoB9xHGSjqRXsW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;472&quot; height=&quot;248&quot; data-filename=&quot;화면 캡처 2026-03-30 212040.png&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;과도하게 분포되어있는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AQE 활성화와 비활성화의 차이&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 212122.png&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;469&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k7eSz/dJMcaf0k2Wv/x8ogLeEihCQt0llIj6wIe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k7eSz/dJMcaf0k2Wv/x8ogLeEihCQt0llIj6wIe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k7eSz/dJMcaf0k2Wv/x8ogLeEihCQt0llIj6wIe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk7eSz%2FdJMcaf0k2Wv%2Fx8ogLeEihCQt0llIj6wIe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;656&quot; height=&quot;469&quot; data-filename=&quot;화면 캡처 2026-03-30 212122.png&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;469&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 212136.png&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xkcnG/dJMcagSvPs6/MusBenqzkkZlkZUBSEKKdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xkcnG/dJMcagSvPs6/MusBenqzkkZlkZUBSEKKdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xkcnG/dJMcagSvPs6/MusBenqzkkZlkZUBSEKKdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxkcnG%2FdJMcagSvPs6%2FMusBenqzkkZlkZUBSEKKdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;444&quot; data-filename=&quot;화면 캡처 2026-03-30 212136.png&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4단계에서 다뤘던 데이터 불균형(Skew)을 런타임에 감지하고, 비정상적으로 큰 파티션을 자동으로 쪼개어 병렬 처리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774687993702&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Skew Join 자동 최적화 활성화
spark.conf.set(&quot;spark.sql.adaptive.skewJoin.enabled&quot;, &quot;true&quot;)

# 어느 정도를 Skew로 볼 것인가? (평균의 n배 이상일 때)
spark.conf.set(&quot;spark.sql.adaptive.skewJoin.skewedPartitionFactor&quot;, &quot;5&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;상황:&lt;/b&gt; 특정 키에 데이터가 몰려 특정 태스크만 종료되지 않을 때.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;효과:&lt;/b&gt; Salting 코드를 직접 짜지 않아도 엔진이 알아서 병목을 제거합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전체 구조 요약 및 체크리스트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;10&quot; data-path-to-node=&quot;24&quot;&gt;Spark Performance Tuning 5단계&lt;/b&gt;를 하나의 체크리스트로 묶으면 다음과 같습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;25&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;단계&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;핵심 키워드&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;주요 액션&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,1,0,0&quot;&gt;1. Compute&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,1,1,0&quot;&gt;Shuffle &amp;amp; Join&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,1,2,0&quot;&gt;Broadcast 활용, 셔플 최소화&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,2,0,0&quot;&gt;2. Data&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,2,1,0&quot;&gt;I/O &amp;amp; Format&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,2,2,0&quot;&gt;Parquet, Partition Pruning, 파일 크기 최적화&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,3,0,0&quot;&gt;3. Execution&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,3,1,0&quot;&gt;Memory&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,3,2,0&quot;&gt;cache() 적재적소 사용, Spill 방지&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,4,0,0&quot;&gt;4. Skew&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,4,1,0&quot;&gt;Bottleneck&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,4,2,0&quot;&gt;Salting 기법, AQE Skew Join 활용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,5,0,0&quot;&gt;5. Runtime&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,5,1,0&quot;&gt;AQE&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;25,5,2,0&quot;&gt;adaptive.enabled=true 및 동적 파티션 병합&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Spark UI 읽는 법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 튜닝의 완성은 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;28&quot;&gt;Spark UI&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,0,0&quot;&gt;SQL Tab:&lt;/b&gt; AQE가 실제로 실행 계획을 어떻게 변경했는지(Adaptive Plan) 확인하세요.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,1,0&quot;&gt;Executors Tab:&lt;/b&gt; 각 Executor의 메모리 사용량과 GC 타임을 확인하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark 애플리케이션의 성능은 단순히 메모리를 늘리는 것으로 해결되지 않습니다. &lt;b data-index-in-node=&quot;46&quot; data-path-to-node=&quot;3&quot;&gt;연산 구조, 데이터 입출력, 메모리 관리, 데이터 불균형, 그리고 런타임 자동 최적화&lt;/b&gt;라는 5가지 레이어를 체계적으로 관리해야 합니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;1. Compute Layer (연산 구조 최적화)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;성능 최적화의 첫걸음은 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;셔플(Shuffle) 최소화&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;Join 전략:&lt;/b&gt; 데이터 크기에 따라 Broadcast Hash Join을 유도하거나, 대용량 간의 조인에서는 Sort Merge Join이 원활하게 돌아가도록 설계합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;파티션 제어:&lt;/b&gt; Repartition과 Coalesce를 적재적소에 사용하여 병렬성을 확보하고 셔플 오버헤드를 줄입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;구조 이해:&lt;/b&gt; DAG와 Stage를 분석하여 &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;7,2,0&quot;&gt;Narrow vs Wide Transformation&lt;/b&gt;을 구분하고 연산 병목 지점을 파악합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;2. Data Layer (I/O 최적화)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 읽고 쓰는 시점의 효율이 전체 속도의 70%를 결정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;저장 포맷:&lt;/b&gt; 열 지향 저장소인 Parquet를 사용하여 &lt;b data-index-in-node=&quot;31&quot; data-path-to-node=&quot;10,0,0&quot;&gt;Column Pruning&lt;/b&gt;을 활성화합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;필터링 최적화:&lt;/b&gt; Predicate Pushdown과 Partition Pruning을 통해 필요한 데이터만 골라 읽어 메모리 낭비를 방지합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;파일 관리:&lt;/b&gt; &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;10,2,0&quot;&gt;Small File&lt;/b&gt; 문제를 해결하여 네임노드 부하를 줄이고 스캔 속도를 극대화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;3. Execution Layer (메모리 및 실행 효율)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;적재된 데이터를 어떻게 효율적으로 재사용하고 관리하느냐가 핵심입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;캐시 전략:&lt;/b&gt; cache()와 persist()의 차이를 이해하고, 반복 사용되는 데이터의 &lt;b data-index-in-node=&quot;50&quot; data-path-to-node=&quot;13,0,0&quot;&gt;StorageLevel&lt;/b&gt;을 최적화합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;메모리 분배:&lt;/b&gt; &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;13,1,0&quot;&gt;Execution vs Storage Memory&lt;/b&gt; 비율을 조정하여 연산 중 발생하는 &lt;b data-index-in-node=&quot;55&quot; data-path-to-node=&quot;13,1,0&quot;&gt;Disk Spill&lt;/b&gt;을 원천 차단합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;Off-heap:&lt;/b&gt; JVM 밖의 메모리를 활용해 GC(Garbage Collection) 부하를 줄입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;4. Skew Handling (병목 제거)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;가장 늦게 끝나는 태스크가 전체 종료 시간을 결정한다&lt;/span&gt;&lt;/b&gt;는 원칙에 따라 데이터 불균형을 잡습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;데이터 분산:&lt;/b&gt; &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;16,0,0&quot;&gt;Salting&lt;/b&gt; 기법을 통해 특정 키에 몰린 데이터를 강제로 분산시킵니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;탐지 및 격리:&lt;/b&gt; Spark UI를 통해 &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;16,1,0&quot;&gt;Skew&lt;/b&gt;를 감지하고, Heavy Key(NULL 등)를 분리하여 별도로 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,2,0&quot;&gt;AQE 활용:&lt;/b&gt; 엔진 레벨에서 지원하는 자동 Skew 최적화 옵션을 활성화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;5. Runtime Optimization (AQE 자동 최적화)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;Spark 3.x의 정수인 Adaptive Query Execution(AQE)을 통해 실행 중에 스스로 최적화합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;동적 병합:&lt;/b&gt; 셔플 후 작아진 파티션들을 자동으로 합쳐(Coalescing) 태스크 낭비를 막습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;전략 변경:&lt;/b&gt; 런타임 통계에 따라 조인 방식을 동적으로 변경하여 최적의 경로를 찾습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0&quot;&gt;지능적 대응:&lt;/b&gt; 실행 중 발생하는 다양한 변수에 유연하게 대응하여 수동 튜닝의 한계를 극복합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark 튜닝은 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;21,0&quot;&gt;I/O를 줄이고(Data), 셔플을 방지하며(Compute), 메모리 부족을 해결하고(Execution), 불균형을 해소하여(Skew), 자동화 도구(AQE)를 적극 활용합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spark</category>
      <category>spark</category>
      <category>데이터엔지니어</category>
      <category>스파크</category>
      <category>쿼리튜닝</category>
      <author>jyu_seo_</author>
      <guid isPermaLink="true">https://jyu-seo.tistory.com/182</guid>
      <comments>https://jyu-seo.tistory.com/182#entry182comment</comments>
      <pubDate>Sat, 28 Mar 2026 17:57:00 +0900</pubDate>
    </item>
    <item>
      <title>[Spark] - Spark</title>
      <link>https://jyu-seo.tistory.com/181</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;Spark를 배우면서 정리를 위한 블로그를 작성해봤습니다. 제가 Spark를 배우면서 생각나는것들과 이해를 하기위해서 궁금했던것들을 종합적으로 찾아보고 정리하는 글입니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;2편에서는&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;Spark Performance Tuning &lt;/b&gt;관련된 긴 글을 작성해보려 합니다.하나하나의 &lt;b&gt;피상적 정리&lt;/b&gt;를 벗어나 이론보다는 전체적인 그림과 연결점을 찾는 과정이라 생각해주시면 감사하겠습니다 :)&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt;Spark란&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;Apache Spark는 대용량 데이터를 빠르게 처리하기 위한 분산처리 프레임워크&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt;Spark의 핵심개념&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분산처리 : 여러서버에서 동시에 처리하는방식&lt;/li&gt;
&lt;li&gt;인메모리:&amp;nbsp; 데이터를 디스크가 아니라 RAM에 올려서 처리&lt;/li&gt;
&lt;li&gt;RDD: 여러 노드(서버)에 분산된, 변경 불가능한 데이터 집합&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt;분산처리의 장점&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수평적 확장성(&lt;b data-start-index=&quot;171&quot;&gt;Scale-out&lt;/b&gt;) : &lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;190&quot;&gt;단일 머신은 CPU나 RAM 확장에 물리적 한계가 있지만, 분산 환경에서는 노드(서버)를 추가함으로써 &lt;/span&gt;&lt;b data-start-index=&quot;248&quot;&gt;메모리와 CPU 자원을 거의 무한하게 확장&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;271&quot;&gt;할 수 있습니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-start-index=&quot;280&quot;&gt;병렬 처리를 통한 속도 향상 : &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;296&quot;&gt;데이터를 &lt;/span&gt;&lt;b&gt;파티션(Partition)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;316&quot;&gt; 단위로 나누어 여러 노드에서 동시에&lt;b&gt; 태스크(Task)&lt;/b&gt;를 실행합니다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;357&quot;&gt;. 이로 인해 처리 속도가 선형적으로 증가하며 대규모 작업을 빠르게 완료할 수 있습니다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;405&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;405&quot;&gt; &lt;b data-start-index=&quot;406&quot;&gt;고가용성 및 복구:&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot;&gt;데이터가 여러 파티션으로 분산되어 관리되므로, 특정 노드에 장애가 발생하더라도 &lt;b&gt;계보(Lineage)를&lt;/b&gt; 통해 유실된 파티션만 재계산하여 복구할 수 있는 안정성을 갖춥니다&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt;인메모리(In-memory) 방식의 장점&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-start-index=&quot;582&quot;&gt;획기적인 처리 속도:&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;593&quot;&gt; 데이터를 처리할 때마다 디스크 I/O가 발생하는 Hadoop MapReduce와 같은 디스크 기반 방식에 비해 &lt;/span&gt;&lt;b data-start-index=&quot;656&quot;&gt;매우 빠른 속도&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;664&quot;&gt;를 자랑합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;664&quot;&gt; &lt;b data-start-index=&quot;672&quot;&gt;반복 연산 및 반복 알고리즘에 최적:&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;692&quot;&gt; 동일한 데이터를 여러 번 참조해야 하는 머신러닝(ML)이나 반복적인 데이터 가공 작업에서 효율이 극대화됩니다&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;664&quot;&gt; &lt;b data-start-index=&quot;754&quot;&gt;데이터 캐싱(Caching):&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;770&quot;&gt; 반복적으로 사용하는 RDD나 DataFrame을 메모리에 &lt;b&gt;캐싱(Cache/Persist)&lt;/b&gt;하여 저장해두면, 이후 작업에서 재계산 없이 즉시 데이터를 불러와 성능 병목을 방지할 수 있습니다&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;664&quot;&gt; &lt;b data-start-index=&quot;879&quot;&gt;자원 최적화:&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;886&quot;&gt; &lt;/span&gt;&lt;b data-start-index=&quot;887&quot;&gt;지연 평가(Lazy Evaluation)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #303030; text-align: start;&quot; data-start-index=&quot;909&quot;&gt; 방식을 채택하여 실제 연산이 필요한 시점(Action)까지 실행을 미루고 전체 실행 계획을 최적화함으로써, 불필요한 연산을 줄이고 메모리를 더 효율적으로 사용합니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt;Spark의 핵심 컴포넌트&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 126px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;컴포넌트&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;Spark Core&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;RDD 기반 기본 엔진&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;Spark SQL&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;DataFrame, SQL 쿼리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;Spark Streaming&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;실시간 데이터 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;MLlib&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;머신러닝 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;GraphX&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;그래프 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt;Spark를 사용하는 이유는?&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 한 서버 메모리&lt;/li&gt;
&lt;li&gt;CPU 병렬 처리 한계&lt;/li&gt;
&lt;li&gt;분산 환경에서 처리 + 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1060&quot; data-start=&quot;895&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.4419%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 35.6977%;&quot;&gt;단일머신&lt;/td&gt;
&lt;td style=&quot;width: 41.8605%;&quot;&gt;Spark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;986&quot; data-start=&quot;965&quot;&gt;
&lt;td style=&quot;width: 22.4419%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;971&quot; data-start=&quot;965&quot;&gt;메모리&lt;/td&gt;
&lt;td style=&quot;width: 35.6977%;&quot; data-end=&quot;977&quot; data-start=&quot;971&quot; data-col-size=&quot;sm&quot;&gt;제한적&lt;/td&gt;
&lt;td style=&quot;width: 41.8605%;&quot; data-end=&quot;986&quot; data-start=&quot;977&quot; data-col-size=&quot;sm&quot;&gt;노드 합산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1012&quot; data-start=&quot;987&quot;&gt;
&lt;td style=&quot;width: 22.4419%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;993&quot; data-start=&quot;987&quot;&gt;CPU&lt;/td&gt;
&lt;td style=&quot;width: 35.6977%;&quot; data-end=&quot;1003&quot; data-start=&quot;993&quot; data-col-size=&quot;sm&quot;&gt;코어 수 제한&lt;/td&gt;
&lt;td style=&quot;width: 41.8605%;&quot; data-end=&quot;1012&quot; data-start=&quot;1003&quot; data-col-size=&quot;sm&quot;&gt;병렬 확장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1038&quot; data-start=&quot;1013&quot;&gt;
&lt;td style=&quot;width: 22.4419%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1021&quot; data-start=&quot;1013&quot;&gt;처리 속도&lt;/td&gt;
&lt;td style=&quot;width: 35.6977%;&quot; data-end=&quot;1029&quot; data-start=&quot;1021&quot; data-col-size=&quot;sm&quot;&gt;선형 증가&lt;/td&gt;
&lt;td style=&quot;width: 41.8605%;&quot; data-end=&quot;1038&quot; data-start=&quot;1029&quot; data-col-size=&quot;sm&quot;&gt;병렬 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1060&quot; data-start=&quot;1039&quot;&gt;
&lt;td style=&quot;width: 22.4419%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1045&quot; data-start=&quot;1039&quot;&gt;확장성&lt;/td&gt;
&lt;td style=&quot;width: 35.6977%;&quot; data-end=&quot;1051&quot; data-start=&quot;1045&quot; data-col-size=&quot;sm&quot;&gt;제한적&lt;/td&gt;
&lt;td style=&quot;width: 41.8605%;&quot; data-end=&quot;1060&quot; data-start=&quot;1051&quot; data-col-size=&quot;sm&quot;&gt;거의 무한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.4419%;&quot;&gt;확장 방식&lt;/td&gt;
&lt;td style=&quot;width: 35.6977%;&quot;&gt;Scale-up(CPU/RAM 증가)&lt;/td&gt;
&lt;td style=&quot;width: 41.8605%;&quot;&gt;노드 x 코어&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt; Spark vs 주요 데이터 처리 도구 비교&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt;Spark&lt;/span&gt;&lt;/span&gt; vs &lt;span&gt;&lt;span&gt;Hadoop MapReduce&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;435&quot; data-start=&quot;246&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;Spark&lt;/td&gt;
&lt;td&gt;MapReduce&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;328&quot; data-start=&quot;301&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;309&quot; data-start=&quot;301&quot;&gt;처리 방식&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;318&quot; data-start=&quot;309&quot;&gt;메모리 기반&lt;/td&gt;
&lt;td data-end=&quot;328&quot; data-start=&quot;318&quot; data-col-size=&quot;sm&quot;&gt;디스크 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;348&quot; data-start=&quot;329&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;334&quot; data-start=&quot;329&quot;&gt;속도&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;342&quot; data-start=&quot;334&quot;&gt;매우 빠름&lt;/td&gt;
&lt;td data-end=&quot;348&quot; data-start=&quot;342&quot; data-col-size=&quot;sm&quot;&gt;느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;376&quot; data-start=&quot;349&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;354&quot; data-start=&quot;349&quot;&gt;구조&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;360&quot; data-start=&quot;354&quot;&gt;DAG&lt;/td&gt;
&lt;td data-end=&quot;376&quot; data-start=&quot;360&quot; data-col-size=&quot;sm&quot;&gt;Map &amp;rarr; Reduce&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;400&quot; data-start=&quot;377&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;385&quot; data-start=&quot;377&quot;&gt;반복 연산&lt;/td&gt;
&lt;td data-end=&quot;390&quot; data-start=&quot;385&quot; data-col-size=&quot;sm&quot;&gt;강함&lt;/td&gt;
&lt;td data-end=&quot;400&quot; data-start=&quot;390&quot; data-col-size=&quot;sm&quot;&gt;매우 비효율&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;435&quot; data-start=&quot;401&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;407&quot; data-start=&quot;401&quot;&gt;사용성&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;429&quot; data-start=&quot;407&quot;&gt;높음 (SQL, DataFrame)&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;435&quot; data-start=&quot;429&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mapreduce = 레거시&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774696024321&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map &amp;rarr; (디스크 저장) &amp;rarr; Reduce &amp;rarr; (디스크 저장)&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;Spark = 대용량 ETL / 배치처리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774696035150&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Transformation &amp;rarr; 메모리 &amp;rarr; 계속 이어서 처리&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt;Spark vs &lt;span&gt;&lt;span&gt;Apache Flink&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;702&quot; data-start=&quot;535&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;Spark&lt;/td&gt;
&lt;td&gt;Flink&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;621&quot; data-start=&quot;581&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;589&quot; data-start=&quot;581&quot;&gt;처리 방식&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;603&quot; data-start=&quot;589&quot;&gt;Micro-batch&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;621&quot; data-start=&quot;603&quot;&gt;True streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;652&quot; data-start=&quot;622&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;636&quot; data-start=&quot;622&quot;&gt;지연(latency)&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;643&quot; data-start=&quot;636&quot;&gt;초~수초&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;652&quot; data-start=&quot;643&quot;&gt;ms 수준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;676&quot; data-start=&quot;653&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;661&quot; data-start=&quot;653&quot;&gt;상태 관리&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;667&quot; data-start=&quot;661&quot;&gt;제한적&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;676&quot; data-start=&quot;667&quot;&gt;매우 강력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;702&quot; data-start=&quot;677&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;686&quot; data-start=&quot;677&quot;&gt;사용 난이도&lt;/td&gt;
&lt;td data-end=&quot;695&quot; data-start=&quot;686&quot; data-col-size=&quot;sm&quot;&gt;비교적 쉬움&lt;/td&gt;
&lt;td data-end=&quot;702&quot; data-start=&quot;695&quot; data-col-size=&quot;sm&quot;&gt;어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flink = 실시간 - 초저지연 실시간&lt;/li&gt;
&lt;li&gt;Spark = 대부분의 배치 + 준실시간 - 배치중심&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Flink를 쓰는 경우&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1228&quot; data-start=&quot;1214&quot; data-section-id=&quot;qfe62w&quot;&gt;실시간 이벤트 처리&lt;/li&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1229&quot; data-section-id=&quot;19hbqjr&quot;&gt;금융 / 로그 분석&lt;/li&gt;
&lt;li data-end=&quot;1263&quot; data-start=&quot;1244&quot; data-section-id=&quot;1hdqale&quot;&gt;사용자 행동 tracking&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774696729602&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Spark &amp;rarr; 데이터 가공 (ETL)
   &amp;darr;
저장 (Data Lake)
   &amp;darr;
Flink &amp;rarr; 실시간 처리&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt; Spark vs &lt;span&gt;&lt;span&gt;Presto&lt;/span&gt;&lt;/span&gt; / &lt;span&gt;&lt;span&gt;Trino&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1064&quot; data-start=&quot;871&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;Spark&lt;/td&gt;
&lt;td&gt;Presto/Trino&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;960&quot; data-start=&quot;931&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;936&quot; data-start=&quot;931&quot;&gt;목적&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;951&quot; data-start=&quot;936&quot;&gt;데이터 처리 (ETL)&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;960&quot; data-start=&quot;951&quot;&gt;쿼리 엔진&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;991&quot; data-start=&quot;961&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;970&quot; data-start=&quot;961&quot;&gt;데이터 저장&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;981&quot; data-start=&quot;970&quot;&gt;결과 저장 가능&lt;/td&gt;
&lt;td data-end=&quot;991&quot; data-start=&quot;981&quot; data-col-size=&quot;sm&quot;&gt;저장 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1032&quot; data-start=&quot;992&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1000&quot; data-start=&quot;992&quot;&gt;처리 방식&lt;/td&gt;
&lt;td data-end=&quot;1011&quot; data-start=&quot;1000&quot; data-col-size=&quot;sm&quot;&gt;batch 처리&lt;/td&gt;
&lt;td data-end=&quot;1032&quot; data-start=&quot;1011&quot; data-col-size=&quot;sm&quot;&gt;interactive query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1064&quot; data-start=&quot;1033&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1038&quot; data-start=&quot;1033&quot;&gt;속도&lt;/td&gt;
&lt;td data-end=&quot;1051&quot; data-start=&quot;1038&quot; data-col-size=&quot;sm&quot;&gt;무거운 작업에 강함&lt;/td&gt;
&lt;td data-end=&quot;1064&quot; data-start=&quot;1051&quot; data-col-size=&quot;sm&quot;&gt;빠른 조회에 강함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1099&quot; data-start=&quot;1077&quot; data-section-id=&quot;1hnsqlc&quot;&gt;Spark &amp;rarr; 데이터를 만든다 - 데이터 가공&lt;/li&gt;
&lt;li data-end=&quot;1122&quot; data-start=&quot;1100&quot; data-section-id=&quot;oepna0&quot;&gt;Presto &amp;rarr; 데이터를 조회한다 - 데이터 조회&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Spark 실행 구조&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s1Rb0/dJMcaiQiLdH/4WwEkYCjCcZ8RbiNpmt9Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s1Rb0/dJMcaiQiLdH/4WwEkYCjCcZ8RbiNpmt9Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s1Rb0/dJMcaiQiLdH/4WwEkYCjCcZ8RbiNpmt9Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs1Rb0%2FdJMcaiQiLdH%2F4WwEkYCjCcZ8RbiNpmt9Nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1110&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1110&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;흐름방식&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774576522141&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Driver Application &amp;rarr; Cluster Manager &amp;rarr; Worker Node &amp;rarr; Executor &amp;rarr; Task &amp;rarr; Data&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Driver에서 코드 실행&lt;br /&gt;2. Action 발생 &amp;rarr; Job 생성&lt;br /&gt;3. DAG 생성&lt;br /&gt;4. Stage 나눔&lt;br /&gt;5. Task 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Cluster Manager에 요청&lt;br /&gt;7. Executor 생성&lt;br /&gt;8. Task를 Executor에 전달&lt;br /&gt;9. Executor가 Partition 단위로 처리&lt;br /&gt;10. 결과 반환&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Driver Apllication (앱단위)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;355&quot; data-start=&quot;333&quot; data-section-id=&quot;v3qwz2&quot;&gt;하나의 Driver 프로세스 포함&lt;/li&gt;
&lt;li data-end=&quot;376&quot; data-start=&quot;356&quot; data-section-id=&quot;7qle5v&quot;&gt;여러 Job을 포함할 수 있음&lt;/li&gt;
&lt;li data-end=&quot;376&quot; data-start=&quot;356&quot; data-section-id=&quot;7qle5v&quot;&gt;Spark의 &lt;b&gt;두뇌 (컨트롤 타워)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;388&quot; data-start=&quot;343&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;360&quot; data-start=&quot;343&quot; data-section-id=&quot;7oz1nl&quot;&gt;SparkSession 생성&lt;/li&gt;
&lt;li data-end=&quot;369&quot; data-start=&quot;361&quot; data-section-id=&quot;1qmbbib&quot;&gt;Job 생성&lt;/li&gt;
&lt;li data-end=&quot;378&quot; data-start=&quot;370&quot; data-section-id=&quot;1v0gv6u&quot;&gt;DAG 생성&lt;/li&gt;
&lt;li data-end=&quot;388&quot; data-start=&quot;379&quot; data-section-id=&quot;1cb1mmp&quot;&gt;Task 분배&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Driver의 역할(지휘관)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Job을 생성하고, 실행 계획을 만들고, Executor에게 일을 시키는 주체&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spark 작업을 계획하고 실행을 지휘하는 중앙 컨트롤러&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;4,0,0&quot; data-index-in-node=&quot;0&quot;&gt;스파크 세션(Spark Session):&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;스파크 기능에 접속하기 위한 통합 진입점입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;4,1,0&quot; data-index-in-node=&quot;0&quot;&gt;작업 스케줄링:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;코드를 분석하여 실행 계획(DAG)을 세우고, 이를 작은 단위인 '태스크(Task)'로 쪼개어 워커 노드에 할당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;main() 함수 실행: 사용자 프로그램의 진입점&lt;/li&gt;
&lt;li&gt;SparkContext/SparkSession 생성: Spark 클러스터와 연결&lt;/li&gt;
&lt;li&gt;작업 계획 수립: DAG(Directed Acyclic Graph) 생성&lt;/li&gt;
&lt;li&gt;Task 스케줄링: 작업을 Executor에 분배&lt;/li&gt;
&lt;li&gt;Spark UI 제공&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. Job (작업 단위)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Action 실행 시 생성되는 전체 Spark 작업 단위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 실행 요청한 작업을 실제로 실행 단위로 쪼개서 처리하는 시작점입니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F9sWy/dJMcaax0BpY/QkfQ7XFpRzZaJJyDP71Csk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F9sWy/dJMcaax0BpY/QkfQ7XFpRzZaJJyDP71Csk/img.png&quot; data-alt=&quot;이 그림은 액션(Action)이 트리거되었을 때, 드라이버가 데이터를 변형(map)하고 섞고(reduce) 합치는 과정을 하나의 Job으로 설계한 지도입니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F9sWy/dJMcaax0BpY/QkfQ7XFpRzZaJJyDP71Csk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF9sWy%2FdJMcaax0BpY%2FQkfQ7XFpRzZaJJyDP71Csk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;794&quot; height=&quot;368&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이 그림은 액션(Action)이 트리거되었을 때, 드라이버가 데이터를 변형(map)하고 섞고(reduce) 합치는 과정을 하나의 Job으로 설계한 지도입니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot;&gt;Spark는 collect(), count() 같은 액션 연산이 트리거되면, Spark 애플리케이션을 실행하는 책임자이자 모든 Spark 애플리케이션의 진입점으로 간주되는 드라이버 프로그램이 이 Spark 애플리케이션을 아래 그림에서 볼 수 있는 단일 작업으로 변환하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;flatMap(), map():&lt;/b&gt; 데이터를 조각내거나 형태를 바꾸는 과정입니다. 화살표가 1:1로 깔끔하게 이어지는데, 이를 Narrow Dependency(좁은 의존성)라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;스테이지 0 (셔플 전):&lt;/b&gt; 각자 자기 앞에 놓인 데이터만 처리하면 되는 단계입니다. (예: , ) 남의 도움이 필요 없으니 여러 컴퓨터가 동시에 &lt;b data-index-in-node=&quot;90&quot; data-path-to-node=&quot;4,0,0&quot;&gt;병렬&lt;/b&gt;로 아주 빠르게 처리합니다.map,filter&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 셔플(Shuffle)의 등장: reduceByKey()&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;그림의 &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;10&quot;&gt;RDD 3에서 RDD 4로 넘어가는 구간&lt;/b&gt;은 화살표로 엉쳐있는걸 볼수있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;reduceByKey():&lt;/b&gt; 같은 키(Key)를 가진 데이터끼리 합쳐야 할 때 발생합니다.&lt;/li&gt;
&lt;li&gt;이때 데이터가 &lt;b&gt;여러 워커 노드 사이를 이동하며&lt;/b&gt; 재배치되는데, 이것을 &lt;b&gt;셔플(Shuffle)&lt;/b&gt;이라고 합니다.&lt;/li&gt;
&lt;li&gt;Spark 성능에서 가장 비용이 많이 들고 복잡한 구간입니다. (여러 요리사가 각자 썬 재료를 한곳에 모아 섞는 복잡한 과정이라 보시면 됩니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;1423&quot; data-start=&quot;1396&quot; data-section-id=&quot;aaeorw&quot; data-ke-size=&quot;size20&quot;&gt;여러 Job이 동시에 실행될 수 있음&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1460&quot; data-start=&quot;1424&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1445&quot; data-start=&quot;1424&quot; data-section-id=&quot;1fmy8dx&quot;&gt;특히 클러스터 환경에서 Spark는 병렬 Job 실행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Job의 역할&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 트리거 역할 (Action 기준) : Job = &amp;ldquo;실행을 시작시키는 단위&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAG를 기반으로 실행 계획 생성 :Spark 내부에서는 Transformation들을 쭉 쌓아두고 있다가 Action이 나오면 &lt;b&gt;DAG (Directed Acyclic Graph)&lt;/b&gt; 를 만든다&lt;/p&gt;
&lt;pre id=&quot;code_1774943643109&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;map &amp;rarr; filter &amp;rarr; join &amp;rarr; groupBy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 하나의 Job으로 묶어서 실행 계획 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stage로 분리하는 기준 제공: Job이 만들어지면&amp;nbsp;Spark는 이 Job을 &lt;b&gt;Stage 단위로 쪼갬 &lt;/b&gt;Shuffle 기준으로 Stage 나눔 Narrow / Wide dependency 기준&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Job이 중요한 이유&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1646&quot; data-start=&quot;1601&quot; data-section-id=&quot;awfyni&quot;&gt;&lt;b&gt;Job 수 많으면 성능 저하&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1646&quot; data-start=&quot;1627&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1646&quot; data-start=&quot;1627&quot; data-section-id=&quot;1tzreku&quot;&gt;불필요한 Action 줄여야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1698&quot; data-start=&quot;1648&quot; data-section-id=&quot;v2rspi&quot;&gt;&lt;b&gt;Job 단위로 로그/모니터링&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1698&quot; data-start=&quot;1674&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1698&quot; data-start=&quot;1674&quot; data-section-id=&quot;i3d1sd&quot;&gt;Spark UI에서 Job 기준으로 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1757&quot; data-start=&quot;1700&quot; data-section-id=&quot;1rnyp8i&quot;&gt;&lt;b&gt;최적화 포인트 시작점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1757&quot; data-start=&quot;1722&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1757&quot; data-start=&quot;1722&quot; data-section-id=&quot;1l8nfl9&quot;&gt;병목 찾을 때 Job &amp;rarr; Stage &amp;rarr; Task 순으로 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Job의 범위&lt;/h4&gt;
&lt;pre id=&quot;code_1775034569909&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DAG &amp;rarr; Stage &amp;rarr; Task&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3. DAG(Directed Acyclic Graph)&lt;/h4&gt;
&lt;p data-end=&quot;261&quot; data-start=&quot;232&quot; data-ke-size=&quot;size16&quot;&gt;DAG는 연산의 흐름을 순서대로 표현한 구조 입니다. = 작업 순서도&lt;/p&gt;
&lt;p data-end=&quot;261&quot; data-start=&quot;232&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;261&quot; data-start=&quot;232&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-size: 1.25em; letter-spacing: -1px;&quot;&gt;DAG의 역할&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;261&quot; data-start=&quot;232&quot;&gt;실행 계획 생성 : 어떤 순서로 실행할지 결정&lt;/li&gt;
&lt;li data-end=&quot;261&quot; data-start=&quot;232&quot;&gt;최적화 : 불필요한 연산 제거, 연산 합치기&lt;/li&gt;
&lt;li data-end=&quot;261&quot; data-start=&quot;232&quot;&gt;Stage 나누기 기준 제공&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;DAG 내부구조&lt;/span&gt;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Logical Plan &amp;amp; Physical Plan&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Logical Plan = &amp;ldquo;무엇을 할지 (What)&amp;rdquo;&lt;br /&gt;Physical Plan = &amp;ldquo;어떻게 할지 (How)&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Logical Plan : 연산의 의미(로직)를 표현한 추상 계획 (데이터를 어떻게 가져오고, 어떻게 변환할지에 대한 선언)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Physical Plan&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 실제로 실행 가능한 구체적인 계획 (실제로 어떤 알고리즘과 리소스를 써서 실행할지 결정된 상태)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-start=&quot;1028&quot; data-end=&quot;1213&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;Logical Plan&lt;/td&gt;
&lt;td&gt;Physical Plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;1124&quot; data-end=&quot;1142&quot;&gt;
&lt;td data-start=&quot;1124&quot; data-end=&quot;1129&quot; data-col-size=&quot;sm&quot;&gt;수준&lt;/td&gt;
&lt;td data-start=&quot;1129&quot; data-end=&quot;1135&quot; data-col-size=&quot;sm&quot;&gt;추상적&lt;/td&gt;
&lt;td data-start=&quot;1135&quot; data-end=&quot;1142&quot; data-col-size=&quot;sm&quot;&gt;구체적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;1143&quot; data-end=&quot;1160&quot;&gt;
&lt;td data-start=&quot;1143&quot; data-end=&quot;1151&quot; data-col-size=&quot;sm&quot;&gt;실행 가능&lt;/td&gt;
&lt;td data-start=&quot;1151&quot; data-end=&quot;1155&quot; data-col-size=&quot;sm&quot;&gt;❌&lt;/td&gt;
&lt;td data-start=&quot;1155&quot; data-end=&quot;1160&quot; data-col-size=&quot;sm&quot;&gt;✔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;1161&quot; data-end=&quot;1180&quot;&gt;
&lt;td data-start=&quot;1161&quot; data-end=&quot;1170&quot; data-col-size=&quot;sm&quot;&gt;최적화 대상&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;1170&quot; data-end=&quot;1174&quot;&gt;✔&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;1174&quot; data-end=&quot;1180&quot;&gt;일부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;1181&quot; data-end=&quot;1213&quot;&gt;
&lt;td data-start=&quot;1181&quot; data-end=&quot;1189&quot; data-col-size=&quot;sm&quot;&gt;포함 정보&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;1189&quot; data-end=&quot;1197&quot;&gt;연산 흐름&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;1197&quot; data-end=&quot;1213&quot;&gt;실행 방식 + 알고리즘&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody data-start=&quot;1104&quot; data-end=&quot;1213&quot;&gt;
&lt;tr data-start=&quot;1104&quot; data-end=&quot;1123&quot;&gt;
&lt;td data-start=&quot;1104&quot; data-end=&quot;1109&quot; data-col-size=&quot;sm&quot;&gt;관점&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;1109&quot; data-end=&quot;1116&quot;&gt;What&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;1116&quot; data-end=&quot;1123&quot;&gt;How&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;4. Stage (실행 단계)&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 스테이지(Stage)를 나누는 기준&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Shuffle(= Wide Dependency)&amp;rdquo;가 발생하는 지점에서 나뉜다.&lt;/p&gt;
&lt;pre id=&quot;code_1775027772012&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;groupBy &amp;rarr; join &amp;rarr; reduceByKey&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;스테이지 0 (셔플 전):&lt;/b&gt; 각자 자기 앞에 놓인 데이터만 처리하면 되는 단계입니다. (예: , ) 남의 도움이 필요 없으니 여러 컴퓨터가 동시에 &lt;b data-index-in-node=&quot;90&quot; data-path-to-node=&quot;4,0,0&quot;&gt;병렬&lt;/b&gt;로 아주 빠르게 처리합니다.mapfilter&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;셔플(Shuffle):&lt;/b&gt;&amp;nbsp;&quot;같은 키를 가진 놈들 다 모여!&quot; 라는 명령이 떨어지면 상황이 바뀝니다. 1번 컴퓨터에 있는 '사과' 데이터와 2번 컴퓨터에 있는 '사과' 데이터가 한곳으로 모여야 합계를 낼 수 있기 때문입니다.reduceByKey&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,2,0&quot;&gt;스테이지 1 (셔플 후):&lt;/b&gt; 데이터가 다 모인 후에야 비로소 다음 계산을 시작할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FIUaE/dJMcafF4428/9zsBNfghF0SISDjN2B8pU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FIUaE/dJMcafF4428/9zsBNfghF0SISDjN2B8pU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FIUaE/dJMcafF4428/9zsBNfghF0SISDjN2B8pU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFIUaE%2FdJMcafF4428%2F9zsBNfghF0SISDjN2B8pU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;794&quot; height=&quot;368&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot;&gt;Spark에는 두 가지 유형의 Stage&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot;&gt;1. Shuffle Map Stage&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이름에서 알 수 있듯이, 이는 셔플 동작을 위한 데이터를 생성하는 스파크 내 한 단계입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 단계의 출력은 이후 단계들의 입력 역할을 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드에서 Stage 0은 셔플 동작을 위한 데이터를 생성하여 Stage 1의 입력 역할을 하므로 ShuffleMapStage 역할을 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr7Tkv/dJMcaiCMb3j/6JCWORRLGsR4cehyKeSmA0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr7Tkv/dJMcaiCMb3j/6JCWORRLGsR4cehyKeSmA0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr7Tkv/dJMcaiCMb3j/6JCWORRLGsR4cehyKeSmA0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr7Tkv%2FdJMcaiCMb3j%2F6JCWORRLGsR4cehyKeSmA0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;301&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot;&gt;2. Spark에서의 결과 Stage&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;작업의 마지막 단계는 RDD에서 함수를 실행하여 행동 연산을 실행합니다(예시에서는 행동 연산이 collect입니다).&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;활성 연산 결과를 계산합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예시에서 1단계는 RDD에 수행된 액션 결과를 제공하기 때문에 ResultStage 역할을 합니다. 우리 코드에서는 데이터 셔플 후에 reduceByKey() 함수를 사용해 유사한 키들이 그룹화되었고, 이 단계에서 collect(action operation) 함수를 사용해 코드의 최종 결과를 얻습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;328&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QdM0T/dJMcafF49ML/ifE0CA94mwmH4MJbTJyNhK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QdM0T/dJMcafF49ML/ifE0CA94mwmH4MJbTJyNhK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QdM0T/dJMcafF49ML/ifE0CA94mwmH4MJbTJyNhK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQdM0T%2FdJMcafF49ML%2FifE0CA94mwmH4MJbTJyNhK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;328&quot; height=&quot;300&quot; data-origin-width=&quot;328&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #383838; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;왜 Stage를 나누냐&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;969&quot; data-start=&quot;934&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;948&quot; data-start=&quot;934&quot; data-section-id=&quot;1ehtnk2&quot;&gt;Shuffle 발생하면&lt;/li&gt;
&lt;li data-end=&quot;969&quot; data-start=&quot;949&quot; data-section-id=&quot;luvfiw&quot;&gt;데이터를 네트워크로 재분배해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1019&quot; data-start=&quot;971&quot; data-ke-size=&quot;size16&quot;&gt;  이 과정은 경계(boundary)가 필요함&lt;br /&gt;  그래서 Stage 나눔&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;5. Task (실제 실행 단위)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Partition을 처리하는 최소 실행 단위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark에서 &lt;b&gt;실제로 실행되는 가장 작은 작업 단위&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Partition 하나당 Task 하나&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6.Cluster Manager&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;클러스터의 CPU/메모리를 관리하고 Executor를 띄워주는 관리자입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Spark는 여러 클러스터 매니저와 함께 동작할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;드라이버와 워커 노드 사이에서 자원을 관리합니다. (예: YARN, Kubernetes, Mesos 등)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;드라이버가 필요한 자원(CPU, 메모리)을 요청하면, 클러스터 매니저가 유효한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;7,0,0&quot; data-index-in-node=&quot;44&quot;&gt;워커 노드&lt;/b&gt;를 찾아 연결해 줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 관리&lt;/li&gt;
&lt;li&gt;Executor 생성&lt;/li&gt;
&lt;li&gt;작업 환경 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;대표적인 Cluster Manager 종류&lt;/h4&gt;
&lt;table id=&quot;2ff5cc73-c327-809e-bf36-d7aaf89736b0&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;클러스터 매니저&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;사용 시나리오&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td id=&quot;iCa|&quot;&gt;&lt;b&gt;local[*]&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;&amp;gt;X&amp;#96;&amp;lt;&quot;&gt;로컬 JVM에서 실행&lt;/td&gt;
&lt;td id=&quot;=]on&quot;&gt;개발, 테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td id=&quot;iCa|&quot;&gt;&lt;b&gt;Standalone&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;&amp;gt;X&amp;#96;&amp;lt;&quot;&gt;Spark 자체 클러스터 매니저&lt;/td&gt;
&lt;td id=&quot;=]on&quot;&gt;간단한 클러스터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td id=&quot;iCa|&quot;&gt;&lt;b&gt;YARN&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;&amp;gt;X&amp;#96;&amp;lt;&quot;&gt;Hadoop의 리소스 매니저&lt;/td&gt;
&lt;td id=&quot;=]on&quot;&gt;Hadoop 환경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td id=&quot;iCa|&quot;&gt;&lt;b&gt;Mesos&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;&amp;gt;X&amp;#96;&amp;lt;&quot;&gt;Apache Mesos&lt;/td&gt;
&lt;td id=&quot;=]on&quot;&gt;대규모 클러스터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td id=&quot;iCa|&quot;&gt;&lt;b&gt;Kubernetes&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;&amp;gt;X&amp;#96;&amp;lt;&quot;&gt;K8s 기반 실행&lt;/td&gt;
&lt;td id=&quot;=]on&quot;&gt;클라우드 네이티브&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Driver와 Cluster Manager 의 관계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;Executor 10개 주세요&amp;rdquo;&lt;/p&gt;
&lt;p data-end=&quot;912&quot; data-start=&quot;889&quot; data-ke-size=&quot;size16&quot;&gt;Cluster Manager가 판단: 가능한 노드 찾고, Executor 띄워줌&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.Executor&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker 노드에서 실제 데이터를 처리하는 프로세스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark에서 Task를 실행하고 데이터를 처리하는 실제 작업 주체&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Worker 노드에서 실행되는 프로세스&lt;/li&gt;
&lt;li data-end=&quot;1102&quot; data-start=&quot;1093&quot; data-section-id=&quot;1cahwlk&quot;&gt;Task 실행 - Driver가 내려준 Task 수행 (map,filter,join,aggregation)&lt;/li&gt;
&lt;li data-end=&quot;1111&quot; data-start=&quot;1103&quot; data-section-id=&quot;yf73wo&quot;&gt;데이터 처리 - Partition 단위로 처리 (task1개 = partition 1개)&lt;/li&gt;
&lt;li data-end=&quot;1120&quot; data-start=&quot;1112&quot; data-section-id=&quot;80u8hg&quot;&gt;메모리 관리 - 내부에서 메모리 나눠서 사용 (cache / persist, shuffle buffer , sort)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Executor 내부구조&lt;/h4&gt;
&lt;pre id=&quot;code_1775042679124&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Executor
 ├── CPU (Core)
 ├── Memory
 │     ├── Execution Memory (연산용)
 │     └── Storage Memory (cache용)
 ├── Task 실행
 ├── Shuffle 처리
 └── Spill (디스크 사용)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Executor&lt;span&gt;&amp;nbsp; 병렬 처리 구조&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1775042765594&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Executor (core 4개)
 ├── Task 1
 ├── Task 2
 ├── Task 3
 ├── Task 4&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Core수&amp;nbsp; = 동시 실행 task 수&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Executor 개수 vs Core 수&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1012&quot; data-start=&quot;992&quot; data-section-id=&quot;hdp75m&quot;&gt;Executor 많음 &amp;rarr; 분산 &amp;uarr;&lt;/li&gt;
&lt;li data-end=&quot;1032&quot; data-start=&quot;1013&quot; data-section-id=&quot;5urscb&quot;&gt;Core 많음 &amp;rarr; 병렬 처리 &amp;uarr;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Executor 와 Driver의 차이점&lt;/h4&gt;
&lt;table style=&quot;background-color: #ffffff; color: #303030; text-align: start; border-collapse: collapse; width: 100%; height: 126px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-start-index=&quot;461&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;491&quot;&gt;&lt;b data-start-index=&quot;491&quot;&gt;항목&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;496&quot;&gt;&lt;b data-start-index=&quot;496&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Driver&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;513&quot;&gt;&lt;b data-start-index=&quot;513&quot;&gt;Executor&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;491&quot;&gt;&lt;b data-start-index=&quot;491&quot;&gt;주요 역할&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;496&quot;&gt;&lt;b data-start-index=&quot;496&quot;&gt;중앙 제어 및 스케줄링 (지휘)&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;513&quot;&gt;&lt;b data-start-index=&quot;513&quot;&gt;실제 연산 및 데이터 처리 (실행)&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;532&quot;&gt;&lt;b data-start-index=&quot;532&quot;&gt;위치&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;534&quot;&gt;&lt;span data-start-index=&quot;534&quot;&gt;사용자 프로그램의 진입점 (&lt;/span&gt;main&lt;span data-start-index=&quot;553&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수 실행)&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;560&quot;&gt;&lt;span data-start-index=&quot;560&quot;&gt;클러스터의 워커 노드(Worker Node)&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;584&quot;&gt;&lt;b data-start-index=&quot;584&quot;&gt;주요 작업&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;589&quot;&gt;&lt;span data-start-index=&quot;589&quot;&gt;DAG 생성, 작업 계획 수립, 태스크 배분&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;613&quot;&gt;&lt;span data-start-index=&quot;613&quot;&gt;태스크 실행, 데이터 캐싱, 결과 저장&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;634&quot;&gt;&lt;b data-start-index=&quot;634&quot;&gt;결과 처리&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;639&quot;&gt;&lt;span data-start-index=&quot;639&quot;&gt;익스큐터들로부터 처리 결과를 수집&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;657&quot;&gt;&lt;span data-start-index=&quot;657&quot;&gt;처리된 결과를 드라이버로 전송&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;673&quot;&gt;&lt;b data-start-index=&quot;673&quot;&gt;진입점&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;676&quot;&gt;SparkContext&lt;span data-start-index=&quot;688&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;/&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;SparkSession&lt;span data-start-index=&quot;703&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;생성&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;706&quot;&gt;&lt;span data-start-index=&quot;706&quot;&gt;드라이버가 전달한 태스크 수신 및 실행&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Partition&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Partition은 Spark에서 데이터를 나누는 최소 단위로, 각 Partition이 하나의 Task로 실행되어 병렬 처리를 가능하게 하며, Partition 수는 성능에 직접적인 영향을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 나눈 물리적/논리적 처리 단위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDD의 데이터는 여러 파티션으로 나뉘어 분산 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티션 = 병렬 처리의 단위&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Partition 수 = Task 수 = 병렬 처리 수&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;551&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TypWa/dJMcabwPP0S/KQR2Jg1tWyK8Mk2noxpFy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TypWa/dJMcabwPP0S/KQR2Jg1tWyK8Mk2noxpFy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TypWa/dJMcabwPP0S/KQR2Jg1tWyK8Mk2noxpFy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTypWa%2FdJMcabwPP0S%2FKQR2Jg1tWyK8Mk2noxpFy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;880&quot; height=&quot;551&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 Partition이 중요하냐?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;병렬 처리의 기준&lt;/li&gt;
&lt;li&gt;성능을 좌우함&lt;/li&gt;
&lt;li&gt;suffle과 직결됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Partition의 결정되는 기준&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 읽을때(HDFS / S3 block 기준) , 파일크기 기반&lt;/li&gt;
&lt;li&gt;Shuffle시&lt;/li&gt;
&lt;li&gt;수동 조정&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;repartition vs Coalesce&lt;/h4&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;844&quot; data-start=&quot;673&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;repartition&lt;/td&gt;
&lt;td&gt;coalesce&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;767&quot; data-start=&quot;738&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;753&quot; data-start=&quot;738&quot;&gt;Partition 증가&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;758&quot; data-start=&quot;753&quot;&gt;가능&lt;/td&gt;
&lt;td data-end=&quot;767&quot; data-start=&quot;758&quot; data-col-size=&quot;sm&quot;&gt;❌ 불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;794&quot; data-start=&quot;768&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;783&quot; data-start=&quot;768&quot;&gt;Partition 감소&lt;/td&gt;
&lt;td data-end=&quot;788&quot; data-start=&quot;783&quot; data-col-size=&quot;sm&quot;&gt;가능&lt;/td&gt;
&lt;td data-end=&quot;794&quot; data-start=&quot;788&quot; data-col-size=&quot;sm&quot;&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;822&quot; data-start=&quot;795&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;805&quot; data-start=&quot;795&quot;&gt;Shuffle&lt;/td&gt;
&lt;td data-end=&quot;813&quot; data-start=&quot;805&quot; data-col-size=&quot;sm&quot;&gt;항상 발생&lt;/td&gt;
&lt;td data-end=&quot;822&quot; data-start=&quot;813&quot; data-col-size=&quot;sm&quot;&gt;거의 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;844&quot; data-start=&quot;823&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;828&quot; data-start=&quot;823&quot;&gt;성능&lt;/td&gt;
&lt;td data-end=&quot;838&quot; data-start=&quot;828&quot; data-col-size=&quot;sm&quot;&gt;느릴 수 있음&lt;/td&gt;
&lt;td data-end=&quot;844&quot; data-start=&quot;838&quot; data-col-size=&quot;sm&quot;&gt;빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;HashPartitioner&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key의 해시값을 이용해 데이터를 파티션에 할당합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,1,0,0&quot;&gt;무작위성:&lt;/b&gt; Key가 무엇이든 상관없이 해시 함수를 통해 파티션에 골고루 뿌려줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,1,1,0&quot;&gt;속도:&lt;/b&gt; 계산이 매우 빠릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774753377624&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;partition = hash(key) % numPartitions&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Range Partitioner&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 값의 범위(Range)를 기준으로 파티션을 나눕니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬된 RDD의 데이터를 거의 같은 범위 간격으로 분할할수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,1,0,0&quot;&gt;정렬 유지:&lt;/b&gt; 각 파티션 내의 데이터뿐만 아니라, &lt;b data-index-in-node=&quot;27&quot; data-path-to-node=&quot;9,1,1,0,0&quot;&gt;파티션 간의 순서&lt;/b&gt;도 유지됩니다. (1번 파티션의 모든 값 &amp;lt; 2번 파티션의 모든 값)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,1,1,0&quot;&gt;샘플링 필요:&lt;/b&gt; 범위를 나누기 위해 데이터를 미리 한 번 훑어보는 과정(Sampling)이 추가로 필요하여 Hash보다 약간의 오버헤드가 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span data-path-to-node=&quot;12,0,1,0&quot;&gt;HashPartitioner VS &lt;/span&gt;&lt;span data-path-to-node=&quot;12,0,2,0&quot;&gt;RangePartitioner&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;HashPartitioner&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;RangePartitioner&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0,0&quot;&gt;핵심 목표&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,1,1,0&quot;&gt;데이터를 균등하게 분산&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,1,2,0&quot;&gt;데이터의 순서(정렬) 유지&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0,0&quot;&gt;결정 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,2,1,0&quot;&gt;해시 함수 결과값&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,2,2,0&quot;&gt;키 값의 범위 (Boundaries)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,3,0,0&quot;&gt;정렬 상태&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,3,1,0&quot;&gt;정렬되지 않음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,3,2,0&quot;&gt;파티션 간/내 정렬됨&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,4,0,0&quot;&gt;주요 연산&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,4,1,0&quot;&gt;groupByKey, reduceByKey&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,4,2,0&quot;&gt;sortByKey&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,5,0,0&quot;&gt;추가 비용&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,5,1,0&quot;&gt;거의 없음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;12,5,2,0&quot;&gt;범위 산정을 위한 데이터 샘플링 비용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;spark-partition의-종류&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Spark Partition의 종류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;Input Partition&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;Output Partition&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;Shuffle Partition&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;이 중, Spark의 주요 연산이 Shuffle인 만큼, Shuffle Partition이 가장 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 id=&quot;input-partition&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Input Partition&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련 설정 : spark.sql.files.maxpartitionBytes&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Input Partition은 처음 파일을 읽을 때 생성하는 Partition입니다. 관련 설정값은 &lt;b&gt;spark.sql.files.maxPartitionBytes&lt;/b&gt;으로, Input Partition의 크기를 설정할 수 있고, 기본값은 134217728(128MB)입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;파일 (HDFS 상의 마지막 경로에 존재하는 파일)의 크기가 128MB보다 크다면, Spark에서 128MB만큼 쪼개면서 파일을 읽습니다. 파일의 크기가 128MB보다 작다면 그대로 읽어 들여, 파일 하나당 Partition 하나가 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;대부분의 경우, 필요한 칼럼만 골라서 뽑아 쓰기 때문에 파일이 128MB보다 작습니다. 가끔씩 큰 파일을 다룰 경우에는 이 설정값을 조절해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs36mm/dJMcajaA6OW/BDScG7ZEevgZ8KQV2zFTIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs36mm/dJMcajaA6OW/BDScG7ZEevgZ8KQV2zFTIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs36mm/dJMcajaA6OW/BDScG7ZEevgZ8KQV2zFTIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs36mm%2FdJMcajaA6OW%2FBDScG7ZEevgZ8KQV2zFTIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;343&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Use Case&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;파일 하나의 크기가 매우 크고 수도 많다면, 설정값 크기를 늘리고 자원도 늘려야 하지만, 제 경험상 이런 경우는 없었습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;또한, 필요한 칼럼(column)만 쓰기 때문에 데이터의 크기는 더 작아집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;output-partition&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Output Partition&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련 설정 : df.repartition(cnt), df.coalesce(cnt)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Output Partition은 파일을 저장할 때 생성하는 Partition입니다&lt;/b&gt;. 이 Partition의 수가 HDFS 상의 마지막 경로의 파일 수를 지정합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로, HDFS는 큰 파일을 다루도록 설계되어 있어, 크기가 큰 파일로 저장하는 것이 좋습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보통 HDFS Blocksize에 맞게 설정하면 되는데, 카카오 Hadoop 클러스터의 HDFS Blocksize는 268435456 (256MB)로 설정되어 있어서, 통상적으로 파일 하나의 크기를 256MB에 맞도록 Partition의 수를 설정하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Partition의 수는 df.repartition(cnt), df.coalesce(cnt)를 통해 설정합니다. 이 repartition와 coalesce를 이용해 Partition 수를 줄일 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래의 예시는, 파일 수를 줄여서 50개로 저장하는 모습입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzclmX/dJMcaiJwDpR/YDnXxSoJkwlSMNmXYimtA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzclmX/dJMcaiJwDpR/YDnXxSoJkwlSMNmXYimtA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzclmX/dJMcaiJwDpR/YDnXxSoJkwlSMNmXYimtA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzclmX%2FdJMcaiJwDpR%2FYDnXxSoJkwlSMNmXYimtA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;334&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;Use Case&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;보통 groupBy 집계 후 저장할 때 데이터의 크기가 작아집니다. 그런 다음 spark.sql.shuffle.partitions 설정에 따라 파일 수가 지정되는데, 이때 파일의 크기를 늘리기 위해 repartition와 coalesce을 사용해 Partition 수를 줄일 수 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;df.where()를 통해 필터링을 하고 나서 그대로 저장한다면 파편화가 생깁니다. 그래서 repartition(cnt)을 한 후 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;shuffle-partition&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Shuffle Partition&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련 설정 : spark.sql.shuffle.partitions&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Spark 성능에 가장 크게 영향을 미치는 Partition으로, &lt;b&gt;Join, groupBy 등의 연산을 수행할 때 Shuffle Partition이 쓰입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;설정값은 spark.sql.shuffle.partitions이고, 이 설정값에 따라 Join, groupBy 수행 시 Partition의 수(또는 Task의 수)가 결정됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMtIXC/dJMcadIdnFP/tLt3szZcwt5RN4y1LDHkzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMtIXC/dJMcadIdnFP/tLt3szZcwt5RN4y1LDHkzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMtIXC/dJMcadIdnFP/tLt3szZcwt5RN4y1LDHkzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMtIXC%2FdJMcadIdnFP%2FtLt3szZcwt5RN4y1LDHkzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;338&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 설정값은 Core 수에 맞게 설정하라고 하지만, Partition의 크기에 맞추어서 설정해야 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 Partition의 크기가 크고 연산에 쓰이는 메모리가 부족하다면 Shuffle Spill(데이터를 직렬화하고 스토리지에 저장, 처리 이후에는 역 직렬 화하고 연산 재개함)이 일어나기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Shuffle Spill이 일어나면, Task가 지연되고 에러가 발생할 수 있습니다. 또한, Hadoop 클러스터의 사용률이 높다면, 연달아 에러가 발생하고 Spark가 강제 종료될 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Memory Limit Over와 같이, Shuffle Spill도 메모리 부족으로 나타나는데, 보통 이에 대한 대응을 Core 당 메모리를 늘리는 것으로 해결합니다. 하지만, 모든 사람이 메모리가 부족하다고 메모리 할당량을 늘린다면, 클러스터가 사용성이 더 떨어지고 작업이 더욱더 실패하게 될 것입니다. 그래서 제 개인적인 생각이기도 하지만, Partition의 크기를 결정하는 옵션인 spark.sql.shuffle.partitions를 우선적으로 고려해 설정해야 한다고 생각합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, 일반적으로, 하나의 Shuffle Partition 크기가 100~200MB 정도 나올 수 있도록 spark.sql.shuffle.partitions 수를 조절하는 것이 최적입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Use Case&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Memory Limit Over, Memory Spill 등 자원 문제가 생길 경우, Shuffle Partition 크기를 우선적으로 고려해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;최적화-실험&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;최적화 실험&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Shuffle Partition 크기가 100~200MB 정도 나올 수 있도록 설정하는 것이 얼마나 중요한지 다음의 최적화 실험을 통해 살펴보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Task &amp;amp; Partition&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;파티션:&lt;/b&gt; 거대한 데이터를 여러 개로 쪼갠 조각입니다. 그림에서 박스가 여러 개인 이유는 데이터를 나누어 처리하기 때문입니다.Partition&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;태스크:&lt;/b&gt; 각 파티션에 대해 수행할 실제 작업(예: 필터링, 합계 구하기)입니다. 그림에서 보듯 태스크는 **디스크(Disk)**나 &lt;b data-index-in-node=&quot;73&quot; data-path-to-node=&quot;14,1,0&quot;&gt;캐시&lt;/b&gt;에 있는 데이터 조각을 가져와 작업을 수행합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SparkContext&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터와 연결하고 Spark 작업을 시작하는 진입점(Entry Point)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark를 실행하기 위한 시작점이자 클러스터와의 연결 객체&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SparkContext 역할&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클러스터 연결 Driver &amp;harr; Worker 연결&lt;/li&gt;
&lt;li&gt;Job 실행 시작&lt;/li&gt;
&lt;li&gt;RDD 생성&lt;/li&gt;
&lt;li&gt;리소스 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774577545103&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;User Code
   &amp;darr;
SparkContext  &amp;larr; 여기
   &amp;darr;
Cluster Manager (YARN / Kubernetes / Standalone)
   &amp;darr;
Executors&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SparkSession&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark 애플리케이션의 통합 진입점(Entry Point)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataFrame, SQL, Streaming 등 모든 기능을 하나로 묶은 인터페이스&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SparkSession 역할&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.Python &amp;lt;-&amp;gt; JVM 간의 Gateway역할(통역사) - 연결통로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 내부적으로 분산 처리 구조를 구현하는 역할(Local Mode)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 자원 및 실행 관리 (Lazy Evalution)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DataFrame 생성&lt;/li&gt;
&lt;li&gt;SQL 실행&lt;/li&gt;
&lt;li&gt;데이터 소스 연결 (Parquet,ORC,JSON,JDBC)&lt;/li&gt;
&lt;li&gt;Streaming 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SparkSession&lt;span&gt;&amp;nbsp; 정의 코드&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1774842416245&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql import SparkSession

# SparkSession은 PySpark 앱의 시작점(Entry Point)
# DataFrame API, SQL, 환경 설정 등을 모두 SparkSession을 통해 수행한다.
spark = (
    SparkSession
        .builder                 # SparkSession 빌더 시작
        .appName(&quot;MySparkApp&quot;)   # Spark UI에서 보일 앱 이름 설정
        # .master(&quot;local[*]&quot;)    # (선택) 로컬 실행 시 CPU 모든 코어 사용
        # .config(&quot;key&quot;, &quot;value&quot;)# (선택) 추가적인 Spark 설정
        # .enableHiveSupport()   # (선택) Hive 기능 필요할 때 활성화
        .getOrCreate()           # 기존 세션이 있으면 가져오고 없으면 새로 생성
)

# Spark 버전, 환경 확인용
print(&quot;Spark Version:&quot;, spark.version)

# 예시: 간단하게 DataFrame 생성해보기
data = [(&quot;Alice&quot;, 29), (&quot;Bob&quot;, 35)]
df = spark.createDataFrame(data, [&quot;name&quot;, &quot;age&quot;])
df.show()

# 작업이 끝나면 세션 종료
spark.stop()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 124858.png&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;673&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k6AF9/dJMcahcM6Ex/BvuujTK0tZesesjW9xgDB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k6AF9/dJMcahcM6Ex/BvuujTK0tZesesjW9xgDB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k6AF9/dJMcahcM6Ex/BvuujTK0tZesesjW9xgDB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk6AF9%2FdJMcahcM6Ex%2FBvuujTK0tZesesjW9xgDB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;964&quot; height=&quot;673&quot; data-filename=&quot;화면 캡처 2026-03-30 124858.png&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;673&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Pandas vs Spark&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;table style=&quot;background-color: #ffffff; color: #000000; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;특징&lt;/td&gt;
&lt;td&gt;Pandas&lt;/td&gt;
&lt;td&gt;PySpark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;정체&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Python 라이브러리&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;JVM 엔진을 제어하는 Python 인터페이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;실행 위치&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Python 프로세스 내부&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;별도의 Java 프로세스(JVM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;메모리 사용&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;OS가 허용하는 범위&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Session 설정에 따라 관리됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;시작 방식&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;import만 하면 실행&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;SparkSession.builder.getOrCreate()&lt;span&gt;&amp;nbsp;&lt;/span&gt;필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;SparkContext VS Spark Session&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SparkContext = 저수준 실행 엔진 진입점 (RDD 중심)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;SparkSession = 통합된 고수준 API (DataFrame/SQL 중심)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SparkContext (과거/저수준)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;308&quot; data-start=&quot;300&quot; data-section-id=&quot;7o0nva&quot;&gt;RDD 생성&lt;/li&gt;
&lt;li data-end=&quot;318&quot; data-start=&quot;309&quot; data-section-id=&quot;1kbv610&quot;&gt;클러스터 연결&lt;/li&gt;
&lt;li data-end=&quot;329&quot; data-start=&quot;319&quot; data-section-id=&quot;asw3cx&quot;&gt;기본 실행 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774578448871&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sc.parallelize([1,2,3])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SparkSession (현재/표준)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;436&quot; data-start=&quot;419&quot; data-section-id=&quot;1jak84&quot;&gt;DataFrame / SQL&lt;/li&gt;
&lt;li data-end=&quot;467&quot; data-start=&quot;437&quot; data-section-id=&quot;9vxtx0&quot;&gt;Catalog / UDF / Streaming 포함&lt;/li&gt;
&lt;li data-end=&quot;491&quot; data-start=&quot;468&quot; data-section-id=&quot;3xd725&quot;&gt;내부적으로 SparkContext 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774578459696&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spark.read.parquet(&quot;file&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;758&quot; data-start=&quot;555&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;SparkContext&lt;/td&gt;
&lt;td&gt;SparkSession&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도입&lt;/td&gt;
&lt;td&gt;Spark.1.x&lt;/td&gt;
&lt;td&gt;Spark 2.0+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;660&quot; data-start=&quot;629&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;634&quot; data-start=&quot;629&quot;&gt;수준&lt;/td&gt;
&lt;td data-end=&quot;646&quot; data-start=&quot;634&quot; data-col-size=&quot;sm&quot;&gt;Low-level&lt;/td&gt;
&lt;td data-end=&quot;660&quot; data-start=&quot;646&quot; data-col-size=&quot;sm&quot;&gt;High-level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;695&quot; data-start=&quot;661&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;670&quot; data-start=&quot;661&quot;&gt;역할&lt;/td&gt;
&lt;td data-end=&quot;676&quot; data-start=&quot;670&quot; data-col-size=&quot;sm&quot;&gt;RDD 생성,저수준API&lt;/td&gt;
&lt;td data-end=&quot;695&quot; data-start=&quot;676&quot; data-col-size=&quot;sm&quot;&gt;통합 진입점 (RDD+DataFrame+SQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;714&quot; data-start=&quot;696&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;702&quot; data-start=&quot;696&quot;&gt;사용성&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;708&quot; data-start=&quot;702&quot;&gt;어렵다&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;714&quot; data-start=&quot;708&quot;&gt;쉽다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;734&quot; data-start=&quot;715&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;723&quot; data-start=&quot;715&quot;&gt;기능 통합&lt;/td&gt;
&lt;td data-end=&quot;728&quot; data-start=&quot;723&quot; data-col-size=&quot;sm&quot;&gt;없음&lt;/td&gt;
&lt;td data-end=&quot;734&quot; data-start=&quot;728&quot; data-col-size=&quot;sm&quot;&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;758&quot; data-start=&quot;735&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;743&quot; data-start=&quot;735&quot;&gt;현재 사용&lt;/td&gt;
&lt;td data-end=&quot;752&quot; data-start=&quot;743&quot; data-col-size=&quot;sm&quot;&gt;거의 안 씀&lt;/td&gt;
&lt;td data-end=&quot;758&quot; data-start=&quot;752&quot; data-col-size=&quot;sm&quot;&gt;표준&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;SparkContext는 RDD 기반의 저수준 실행 인터페이스이고, SparkSession은 이를 포함하여 DataFrame과 SQL 기능을 통합한 고수준 API로 현재 표준이다&quot;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;분산 처리 구조의 가상 구현 (Local Mode)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Spark는 기본적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;여러 대의 컴퓨터를 지휘하는 분산 처리 엔진&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Colab처럼 단일 서버에서 실행하더라도 Spark는 내부적으로 다음 구조를 유지합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클러스터 내부에서 실행되는 방식 (Cluster Mode)&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cluster Mode = Driver가 클러스터 내부에서 실행되는 방식&lt;/p&gt;
&lt;pre id=&quot;code_1775012690488&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[사용자 PC]
   &amp;darr; (submit)
[Cluster Manager]
   &amp;darr;
[Driver (Cluster 내부)]
   &amp;darr;
[Executor들]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Local Mode vs Cluster Mode&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Local Mode vs Cluster Mode = &amp;ldquo;혼자 실행 vs 분산 실행 + Driver 위치 차이&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 143px;&quot; border=&quot;1&quot; data-end=&quot;945&quot; data-start=&quot;748&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Local Mode&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Cluster Mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;843&quot; data-start=&quot;818&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;826&quot; data-start=&quot;818&quot;&gt;실행 환경&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;834&quot; data-start=&quot;826&quot; data-col-size=&quot;sm&quot;&gt;단일 머신&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;843&quot; data-start=&quot;834&quot; data-col-size=&quot;sm&quot;&gt;여러 머신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;869&quot; data-start=&quot;844&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;856&quot; data-start=&quot;844&quot;&gt;Driver 위치&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;861&quot; data-start=&quot;856&quot; data-col-size=&quot;sm&quot;&gt;로컬&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;869&quot; data-start=&quot;861&quot; data-col-size=&quot;sm&quot;&gt;클러스터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;902&quot; data-start=&quot;870&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;881&quot; data-start=&quot;870&quot;&gt;Executor&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;893&quot; data-start=&quot;881&quot;&gt;없음(같이 실행)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;902&quot; data-start=&quot;893&quot;&gt;여러 노드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;920&quot; data-start=&quot;903&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;911&quot; data-start=&quot;903&quot;&gt;분산 처리&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;915&quot; data-start=&quot;911&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;920&quot; data-start=&quot;915&quot; data-col-size=&quot;sm&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;945&quot; data-start=&quot;921&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;926&quot; data-start=&quot;921&quot;&gt;용도&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;935&quot; data-start=&quot;926&quot; data-col-size=&quot;sm&quot;&gt;개발/테스트&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;945&quot; data-start=&quot;935&quot; data-col-size=&quot;sm&quot;&gt;운영/대규모&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Worker Node (실제 서버)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 계산이 수행되는 물리적 또는 가상 서버입니다. 하나의 클러스터에는 수많은 워커 노드가 존재할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RDD&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDD (Resilient Distributed Dataset)는 Spark의 가장 기본적인 데이터 추상화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 서버에 나눠 저장되고, 변경 불가능하며, 장애 발생 시 복구 가능한 데이터&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RDD를 제대로 이해하는 4가지 축&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Distributed (분산 데이터)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;412&quot; data-start=&quot;386&quot; data-section-id=&quot;gm3xbi&quot;&gt;데이터가 여러 Partition으로 나뉨&lt;/li&gt;
&lt;li data-end=&quot;436&quot; data-start=&quot;413&quot; data-section-id=&quot;1sm7q96&quot;&gt;여러 Executor에서 병렬 처리&lt;/li&gt;
&lt;li data-end=&quot;436&quot; data-start=&quot;413&quot; data-section-id=&quot;1sm7q96&quot;&gt;RDD = Partition들의 집합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Immutable (불변성)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 RDD 변경 ❌&lt;/li&gt;
&lt;li&gt;새로운 RDD 생성 ✔&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Lazy Evaluation (지연 실행)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Transformation은 즉시 실행되지 않는다.&lt;/li&gt;
&lt;li&gt;Action이 호출될 때 비로소 실제 연산이 수행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 지연평가인가?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 계획 최적화 가능&lt;/li&gt;
&lt;li&gt;불필요한 연산 제거&lt;/li&gt;
&lt;li&gt;메모리 효율적 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Lineage (복구 메커니즘)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RDD는 데이터 자체를 복제하지 않음 대신 &lt;b&gt;어떻게 만들어졌는지&lt;/b&gt; 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774578903521&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rdd.map().filter()
-- 실행 안됨
rdd.collect()
-- 여기서 실행됨&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1774611671233&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                           RDD                                   │
│                                                                 │
│   ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│   │Partition │  │Partition │  │Partition │  │Partition │        │
│   │    1     │  │    2     │  │    3     │  │    4     │        │
│   │ [A,B,C]  │  │ [D,E,F]  │  │ [G,H,I]  │  │ [J,K,L]  │        │
│   └──────────┘  └──────────┘  └──────────┘  └──────────┘        │
│       &amp;darr;             &amp;darr;             &amp;darr;             &amp;darr;               │
│    Executor 1   Executor 2   Executor 1   Executor 2            │
└─────────────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;DataFrame&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마가 있는 RDD 관계형 데이터베이스 테이블처럼 각 컬럼(column)별로 이름과 타입을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬럼(스키마)을 가진 분산 데이터 테이블 (SQL처럼 다룸)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  RDD = 원시 데이터 덩어리&lt;br /&gt;  DataFrame = &lt;b&gt;엑셀/테이블 형태 데이터&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775097999153&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;| name | age | city |
|------|-----|------|
| Kim  | 30  | Seoul |
| Lee  | 25  | Busan |&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;DataFrame 의 장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Schema : 컬럼 + 데이터 타입 존재&lt;/li&gt;
&lt;li&gt;최적화 엔진 : 쿼리 자동 최적화, 실행 계획 자동 개선&lt;b&gt;(Catalyst Optimizer)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Tungsten 엔진 : 메모리효율 증가, CPU 최적화 증가 , Serialization 최소화&lt;/li&gt;
&lt;li&gt;Lazy Evaluation : Action 전까지 실행 안 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataFrame은 Logical Plan &amp;rarr; Catalyst 최적화 &amp;rarr; Physical Plan &amp;rarr; Tungsten 실행 과정을 거쳐 분산 처리된다&amp;rdquo;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Dataset&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataFrame + 타입 안정성(Java/Scala)을 결합한 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;RDD의 안전성 + DataFrame의 편의성&amp;rdquo;을 합친 것&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;825&quot; data-start=&quot;810&quot; data-section-id=&quot;7yoxpp&quot;&gt;컴파일 시 타입 체크&lt;/li&gt;
&lt;li data-end=&quot;842&quot; data-start=&quot;826&quot; data-section-id=&quot;1aotlyx&quot;&gt;DataFrame보다 안전&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Dataset의 내부구조&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;403&quot; data-start=&quot;380&quot; data-section-id=&quot;9myopv&quot;&gt;분산 데이터 (Partition 기반)&lt;/li&gt;
&lt;li data-end=&quot;420&quot; data-start=&quot;404&quot; data-section-id=&quot;1bcrtck&quot;&gt;Schema (컬럼 구조)&lt;/li&gt;
&lt;li data-end=&quot;445&quot; data-start=&quot;421&quot; data-section-id=&quot;6twrks&quot;&gt;Encoder (객체 &amp;harr; 바이너리 변환)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Dataset과 DataFrame의 차이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataFrame =&amp;nbsp; 엑셀 SQL테이블 느낌이고 컬럼이름으로 기반접근&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataSet = 객체(클래스)기반 코드에서 타입으로 접근&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입안정성이란 DataFrmae은 런타임오류 , DataSet은 컴파일 단계에서 오류가 납니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1551&quot; data-start=&quot;1381&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;DataFrame&lt;/td&gt;
&lt;td&gt;Dataset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1464&quot; data-start=&quot;1440&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1449&quot; data-start=&quot;1440&quot;&gt;타입 안정성&lt;/td&gt;
&lt;td data-end=&quot;1456&quot; data-start=&quot;1449&quot; data-col-size=&quot;sm&quot;&gt;❌ 없음&lt;/td&gt;
&lt;td data-end=&quot;1464&quot; data-start=&quot;1456&quot; data-col-size=&quot;sm&quot;&gt;✅ 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1481&quot; data-start=&quot;1465&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1470&quot; data-start=&quot;1465&quot;&gt;구조&lt;/td&gt;
&lt;td data-end=&quot;1475&quot; data-start=&quot;1470&quot; data-col-size=&quot;sm&quot;&gt;있음&lt;/td&gt;
&lt;td data-end=&quot;1481&quot; data-start=&quot;1475&quot; data-col-size=&quot;sm&quot;&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1499&quot; data-start=&quot;1482&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1488&quot; data-start=&quot;1482&quot;&gt;최적화&lt;/td&gt;
&lt;td data-end=&quot;1493&quot; data-start=&quot;1488&quot; data-col-size=&quot;sm&quot;&gt;있음&lt;/td&gt;
&lt;td data-end=&quot;1499&quot; data-start=&quot;1493&quot; data-col-size=&quot;sm&quot;&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1517&quot; data-start=&quot;1500&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1506&quot; data-start=&quot;1500&quot;&gt;사용성&lt;/td&gt;
&lt;td data-end=&quot;1511&quot; data-start=&quot;1506&quot; data-col-size=&quot;sm&quot;&gt;쉬움&lt;/td&gt;
&lt;td data-end=&quot;1517&quot; data-start=&quot;1511&quot; data-col-size=&quot;sm&quot;&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1551&quot; data-start=&quot;1518&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1526&quot; data-start=&quot;1518&quot;&gt;언어 지원&lt;/td&gt;
&lt;td data-end=&quot;1538&quot; data-start=&quot;1526&quot; data-col-size=&quot;sm&quot;&gt;Python 가능&lt;/td&gt;
&lt;td data-end=&quot;1551&quot; data-start=&quot;1538&quot; data-col-size=&quot;sm&quot;&gt;Python 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Encoder란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Encoder는 Dataset에서 객체와 Spark의 내부 바이너리 포맷 간 변환을 담당하여 성능과 타입 안정성을 동시에 지원하는 핵심 컴포넌트입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 객체 &amp;harr; Spark 내부 포맷 변환 역할&lt;/p&gt;
&lt;pre id=&quot;code_1774958076496&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;case class User(name: String, age: Int)

val ds = spark.createDataset(Seq(User(&quot;kim&quot;, 28)))&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;696&quot; data-start=&quot;662&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;696&quot; data-start=&quot;662&quot; data-section-id=&quot;1ts66f3&quot;&gt;User 객체 &amp;rarr; Encoder &amp;rarr; Spark 내부 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Serialization란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체(Object)를 네트워크 전송이나 저장을 위해 바이트 형태로 변환하는 과정&lt;/p&gt;
&lt;pre id=&quot;code_1775105115781&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Driver &amp;rarr; Worker Node (데이터/코드 전달)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;387&quot; data-start=&quot;324&quot; data-ke-size=&quot;size16&quot;&gt;✔ 객체 &amp;rarr; 바이트 변환 (Serialization)&lt;br /&gt;✔ 바이트 &amp;rarr; 객체 복원 (Deserialization)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Encoder vs Serialization 차이&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1276&quot; data-start=&quot;1118&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;Serialization&lt;/td&gt;
&lt;td&gt;Encoder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1206&quot; data-start=&quot;1184&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1189&quot; data-start=&quot;1184&quot;&gt;대상&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1195&quot; data-start=&quot;1189&quot;&gt;RDD&lt;/td&gt;
&lt;td data-end=&quot;1206&quot; data-start=&quot;1195&quot; data-col-size=&quot;sm&quot;&gt;Dataset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1230&quot; data-start=&quot;1207&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1212&quot; data-start=&quot;1207&quot;&gt;목적&lt;/td&gt;
&lt;td data-end=&quot;1220&quot; data-start=&quot;1212&quot; data-col-size=&quot;sm&quot;&gt;객체 전송&lt;/td&gt;
&lt;td data-end=&quot;1230&quot; data-start=&quot;1220&quot; data-col-size=&quot;sm&quot;&gt;내부 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1253&quot; data-start=&quot;1231&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1236&quot; data-start=&quot;1231&quot;&gt;구조&lt;/td&gt;
&lt;td data-end=&quot;1244&quot; data-start=&quot;1236&quot; data-col-size=&quot;sm&quot;&gt;전체 객체&lt;/td&gt;
&lt;td data-end=&quot;1253&quot; data-start=&quot;1244&quot; data-col-size=&quot;sm&quot;&gt;컬럼 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1276&quot; data-start=&quot;1254&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1259&quot; data-start=&quot;1254&quot;&gt;성능&lt;/td&gt;
&lt;td data-end=&quot;1270&quot; data-start=&quot;1259&quot; data-col-size=&quot;sm&quot;&gt;상대적으로 느림&lt;/td&gt;
&lt;td data-end=&quot;1276&quot; data-start=&quot;1270&quot; data-col-size=&quot;sm&quot;&gt;빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Lazy Evaluation&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lazy Evaluation은 연산을 즉시 실행하지 않고 Action 시점까지 미루어, 전체 실행 계획을 최적화한 뒤 수행하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 실행계획을 저장하고 관리하는 공간이 SparkSession이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 Lazy Evaluation을 쓰냐&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 전에 전체 계획을 본다&lt;/p&gt;
&lt;pre id=&quot;code_1774596604458&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;filter &amp;rarr; select &amp;rarr; groupBy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;768&quot; data-start=&quot;754&quot; data-section-id=&quot;l002c9&quot;&gt;filter 먼저 실행&lt;/li&gt;
&lt;li data-end=&quot;781&quot; data-start=&quot;769&quot; data-section-id=&quot;1k0yngw&quot;&gt;불필요한 컬럼 제거&lt;/li&gt;
&lt;li data-end=&quot;792&quot; data-start=&quot;782&quot; data-section-id=&quot;i739xx&quot;&gt;연산 순서 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Catalyst&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataFrame / SQL 쿼리를 실행 계획으로 변환하는 쿼리 처리 프레임워크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 분석하고, 최적화하고, 실행 계획으로 바꾸는 엔진&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Catalyst = 전체 엔진&lt;/li&gt;
&lt;li&gt;Catalyst Optimizer = 그 안의 일부(최적화 담당)&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;742&quot; data-start=&quot;576&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;Catalyst&lt;/td&gt;
&lt;td&gt;Catalyst Optimizer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;677&quot; data-start=&quot;655&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;660&quot; data-start=&quot;655&quot;&gt;범위&lt;/td&gt;
&lt;td data-end=&quot;668&quot; data-start=&quot;660&quot; data-col-size=&quot;sm&quot;&gt;전체 엔진&lt;/td&gt;
&lt;td data-end=&quot;677&quot; data-start=&quot;668&quot; data-col-size=&quot;sm&quot;&gt;일부 모듈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;701&quot; data-start=&quot;678&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;683&quot; data-start=&quot;678&quot;&gt;역할&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;694&quot; data-start=&quot;683&quot;&gt;쿼리 처리 전체&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;701&quot; data-start=&quot;694&quot;&gt;최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;742&quot; data-start=&quot;702&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;710&quot; data-start=&quot;702&quot;&gt;포함 관계&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;725&quot; data-start=&quot;710&quot;&gt;Optimizer 포함&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;742&quot; data-start=&quot;725&quot;&gt;Catalyst에 포함됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Catalyst&lt;span&gt;&amp;nbsp;전체구조&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1774674494902&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;User Code (DataFrame / SQL)
   &amp;darr;
Catalyst
 ├─ Parser
 ├─ Analyzer
 ├─ Optimizer
 └─ Planner
   &amp;darr;
Execution (Task 실행)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot; ♀️-카탈리스트의-확장-가능한-설계의-목적&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;카탈리스트의 확장 가능한 설계의 목적&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Spark SQL에 새로운 최적화 요소나 기술 추가를 쉽게 할 수 있게 하기&lt;/li&gt;
&lt;li&gt;외부 개발자들이 Optimizer를 확장시키는 것을 가능하게 하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;Catalyst에서는 4개 부분으로 나누어 Tree에 Transformation을 수행한다&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Analysis&lt;/li&gt;
&lt;li&gt;Logical Plan Optimization&lt;/li&gt;
&lt;li&gt;Physical Planning&lt;/li&gt;
&lt;li&gt;Code Generation&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Catalyst Optimizer&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 작성한 연산을 더 빠르게 실행되도록 재구성하는 최적화 엔진&lt;/p&gt;
&lt;pre id=&quot;code_1774597003230&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;User Code (DataFrame / SQL)
   &amp;darr;
Logical Plan 추상적인 연산 계획 생성
   &amp;darr;
Analysis 컬럼 존재 여부 확인 / 타입체크
   &amp;darr;
Logical Optimization   &amp;larr; Catalyst 성능이 결정됨
   &amp;darr;
Physical Plan 선택 &amp;ldquo;어떻게 실행할지&amp;rdquo; 결정
   &amp;darr;
Execution 실제 Task 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcSQab/dJMcah4VfzM/cHsNKJSXAlxVurgaVGCG5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcSQab/dJMcah4VfzM/cHsNKJSXAlxVurgaVGCG5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcSQab/dJMcah4VfzM/cHsNKJSXAlxVurgaVGCG5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcSQab%2FdJMcah4VfzM%2FcHsNKJSXAlxVurgaVGCG5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;315&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tungsten&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tungsten은 Spark의 실행엔진으로, 바이너리 기반 데이터 처리와 off-heap 메모리 관리, whole-stage code generation을 통해&amp;nbsp; &lt;br /&gt;Cpu와&amp;nbsp;메모리를&amp;nbsp;직접&amp;nbsp;제어하여&amp;nbsp;Spark&amp;nbsp;연산을&amp;nbsp;최대한&amp;nbsp;빠르게&amp;nbsp;실행하는&amp;nbsp;저수준&amp;nbsp;실행&amp;nbsp;엔진&amp;nbsp;으로&amp;nbsp;스파크&amp;nbsp;성능을&amp;nbsp;실제로&amp;nbsp;만들어&amp;nbsp;내는&amp;nbsp;실행엔진&amp;nbsp;입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Tungsten이 왜 나왔냐&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존문제&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;358&quot; data-start=&quot;341&quot; data-section-id=&quot;7enhop&quot;&gt;JVM object 사용&lt;/li&gt;
&lt;li data-end=&quot;391&quot; data-start=&quot;359&quot; data-section-id=&quot;25l57j&quot;&gt;GC (Garbage Collection) 비용 큼&lt;/li&gt;
&lt;li data-end=&quot;403&quot; data-start=&quot;392&quot; data-section-id=&quot;gn3acc&quot;&gt;메모리 비효율&lt;/li&gt;
&lt;li data-end=&quot;417&quot; data-start=&quot;404&quot; data-section-id=&quot;1xpupp5&quot;&gt;CPU 활용 낮음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결목표&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;447&quot; data-start=&quot;434&quot; data-section-id=&quot;1w1zv6s&quot;&gt;메모리 직접 관리&lt;/li&gt;
&lt;li data-end=&quot;464&quot; data-start=&quot;448&quot; data-section-id=&quot;11i29hz&quot;&gt;CPU cache 활용&lt;/li&gt;
&lt;li data-end=&quot;478&quot; data-start=&quot;465&quot; data-section-id=&quot;qnpvtd&quot;&gt;object 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Tungsten 핵심 기능&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Off-Heap Memory (메모리 직접관리) : JMV heap 밖에서 메모리 사용, Gc부담 감소, 메모리 효율 증가&lt;/li&gt;
&lt;li&gt;Binary Processing (객체 제거) : 객체(Object)가 아닌 &amp;ldquo;바이너리 형태&amp;rdquo;로 데이터를 처리하는 방식([kim][30], 연속된 메모리 배열)&lt;/li&gt;
&lt;li&gt;Whole-Stage Code Generation : 여러 연산을 하나의 Java 코드로 합쳐서 실행하는 최적화 기법&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1152&quot; data-start=&quot;1127&quot; data-section-id=&quot;1dvnbf9&quot; data-ke-size=&quot;size23&quot;&gt;Catalyst vs Tungsten&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1340&quot; data-start=&quot;1154&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;Catalyst&lt;/td&gt;
&lt;td&gt;Tungsten&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1238&quot; data-start=&quot;1214&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1219&quot; data-start=&quot;1214&quot;&gt;역할&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1228&quot; data-start=&quot;1219&quot;&gt;계획 최적화&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1238&quot; data-start=&quot;1228&quot;&gt;실행 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1279&quot; data-start=&quot;1239&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1244&quot; data-start=&quot;1239&quot;&gt;단계&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1270&quot; data-start=&quot;1244&quot;&gt;Logical / Physical Plan&lt;/td&gt;
&lt;td data-end=&quot;1279&quot; data-start=&quot;1270&quot; data-col-size=&quot;sm&quot;&gt;실제 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1306&quot; data-start=&quot;1280&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1285&quot; data-start=&quot;1280&quot;&gt;대상&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1293&quot; data-start=&quot;1285&quot;&gt;쿼리 구조&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1306&quot; data-start=&quot;1293&quot;&gt;CPU / 메모리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1340&quot; data-start=&quot;1307&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1311&quot; data-start=&quot;1307&quot;&gt;예&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1329&quot; data-start=&quot;1311&quot;&gt;filter pushdown&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1340&quot; data-start=&quot;1329&quot;&gt;codegen&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RDD VS DataFrame VS Dataset&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;918&quot; data-start=&quot;611&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;RDD&lt;/td&gt;
&lt;td&gt;DataFrame&lt;/td&gt;
&lt;td&gt;Dataset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;726&quot; data-start=&quot;682&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;687&quot; data-start=&quot;682&quot;&gt;수준&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;699&quot; data-start=&quot;687&quot;&gt;Low-level&lt;/td&gt;
&lt;td data-end=&quot;712&quot; data-start=&quot;699&quot; data-col-size=&quot;sm&quot;&gt;High-level&lt;/td&gt;
&lt;td data-end=&quot;726&quot; data-start=&quot;712&quot; data-col-size=&quot;sm&quot;&gt;High-level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;754&quot; data-start=&quot;727&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;732&quot; data-start=&quot;727&quot;&gt;구조&lt;/td&gt;
&lt;td data-end=&quot;739&quot; data-start=&quot;732&quot; data-col-size=&quot;sm&quot;&gt;❌ 없음&lt;/td&gt;
&lt;td data-end=&quot;746&quot; data-start=&quot;739&quot; data-col-size=&quot;sm&quot;&gt;✅ 있음&lt;/td&gt;
&lt;td data-end=&quot;754&quot; data-start=&quot;746&quot; data-col-size=&quot;sm&quot;&gt;✅ 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;786&quot; data-start=&quot;755&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;764&quot; data-start=&quot;755&quot;&gt;타입 안정성&lt;/td&gt;
&lt;td data-end=&quot;771&quot; data-start=&quot;764&quot; data-col-size=&quot;sm&quot;&gt;❌ 없음&lt;/td&gt;
&lt;td data-end=&quot;778&quot; data-start=&quot;771&quot; data-col-size=&quot;sm&quot;&gt;❌ 없음&lt;/td&gt;
&lt;td data-end=&quot;786&quot; data-start=&quot;778&quot; data-col-size=&quot;sm&quot;&gt;✅ 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;827&quot; data-start=&quot;787&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;793&quot; data-start=&quot;787&quot;&gt;최적화&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;800&quot; data-start=&quot;793&quot;&gt;❌ 없음&lt;/td&gt;
&lt;td data-end=&quot;813&quot; data-start=&quot;800&quot; data-col-size=&quot;sm&quot;&gt;✅ Catalyst&lt;/td&gt;
&lt;td data-end=&quot;827&quot; data-start=&quot;813&quot; data-col-size=&quot;sm&quot;&gt;✅ Catalyst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;864&quot; data-start=&quot;828&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;836&quot; data-start=&quot;828&quot;&gt;실행 엔진&lt;/td&gt;
&lt;td data-end=&quot;841&quot; data-start=&quot;836&quot; data-col-size=&quot;sm&quot;&gt;기본&lt;/td&gt;
&lt;td data-end=&quot;852&quot; data-start=&quot;841&quot; data-col-size=&quot;sm&quot;&gt;Tungsten&lt;/td&gt;
&lt;td data-end=&quot;864&quot; data-start=&quot;852&quot; data-col-size=&quot;sm&quot;&gt;Tungsten&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;888&quot; data-start=&quot;865&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;871&quot; data-start=&quot;865&quot;&gt;사용성&lt;/td&gt;
&lt;td data-end=&quot;877&quot; data-start=&quot;871&quot; data-col-size=&quot;sm&quot;&gt;어려움&lt;/td&gt;
&lt;td data-end=&quot;882&quot; data-start=&quot;877&quot; data-col-size=&quot;sm&quot;&gt;쉬움&lt;/td&gt;
&lt;td data-end=&quot;888&quot; data-start=&quot;882&quot; data-col-size=&quot;sm&quot;&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;918&quot; data-start=&quot;889&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;894&quot; data-start=&quot;889&quot;&gt;언어&lt;/td&gt;
&lt;td data-end=&quot;899&quot; data-start=&quot;894&quot; data-col-size=&quot;sm&quot;&gt;모두&lt;/td&gt;
&lt;td data-end=&quot;904&quot; data-start=&quot;899&quot; data-col-size=&quot;sm&quot;&gt;모두&lt;/td&gt;
&lt;td data-end=&quot;918&quot; data-start=&quot;904&quot; data-col-size=&quot;sm&quot;&gt;Scala/Java&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;RDD는 Spark의 기본 데이터 구조로 불변성과 분산 처리를 제공하지만 최적화가 없어 성능 관리가 어렵습니다. 반면 DataFrame은 스키마 기반 구조를 가지며 Catalyst Optimizer와 Tungsten 엔진을 통해 자동으로 실행 계획이 최적화됩니다. Dataset은 DataFrame에 타입 안정성을 추가하여 컴파일 단계에서 오류를 잡을 수 있습니다. 따라서 실무에서는 생산성과 성능을 고려해 대부분 DataFrame을 사용하고, Scala 환경에서 타입 안정성이 필요할 때 Dataset을 선택합니다.&amp;rdquo;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Transformation&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 RDD를 반환&lt;/li&gt;
&lt;li&gt;지연평가 (Lazy)&lt;/li&gt;
&lt;li&gt;실제 연산은 Action 시점에 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;table id=&quot;2ff5cc73-c327-807a-8cfb-c81e2ec18b9a&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;연산&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-80ff-b564-df89f105a326&quot;&gt;
&lt;td id=&quot;~Uni&quot;&gt;map(f)&lt;/td&gt;
&lt;td id=&quot;LI]]&quot;&gt;각 요소에 함수 f 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-809c-be3a-df7b1202a66b&quot;&gt;
&lt;td id=&quot;~Uni&quot;&gt;filter(f)&lt;/td&gt;
&lt;td id=&quot;LI]]&quot;&gt;조건 f를 만족하는 요소만 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-80d1-9dcc-c200ca799a95&quot;&gt;
&lt;td id=&quot;~Uni&quot;&gt;flatMap(f)&lt;/td&gt;
&lt;td id=&quot;LI]]&quot;&gt;각 요소를 0개 이상의 요소로 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-8025-ba44-e13f405f3149&quot;&gt;
&lt;td id=&quot;~Uni&quot;&gt;distinct()&lt;/td&gt;
&lt;td id=&quot;LI]]&quot;&gt;중복 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-80d1-9863-ff7f8fd69ba2&quot;&gt;
&lt;td id=&quot;~Uni&quot;&gt;union(rdd)&lt;/td&gt;
&lt;td id=&quot;LI]]&quot;&gt;두 RDD 합치기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-80cc-a413-c58fb9d6c8b4&quot;&gt;
&lt;td id=&quot;~Uni&quot;&gt;groupByKey()&lt;/td&gt;
&lt;td id=&quot;LI]]&quot;&gt;키별로 그룹화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-804a-afae-fc5b11d58d3a&quot;&gt;
&lt;td id=&quot;~Uni&quot;&gt;reduceByKey(f)&lt;/td&gt;
&lt;td id=&quot;LI]]&quot;&gt;키별로 값 집계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-8057-b2ac-daecb4bd518c&quot;&gt;
&lt;td id=&quot;~Uni&quot;&gt;sortByKey()&lt;/td&gt;
&lt;td id=&quot;LI]]&quot;&gt;키로 정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Action&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과를 반환하거나 저장&lt;/li&gt;
&lt;li&gt;즉시 실행 (Eager)&lt;/li&gt;
&lt;li&gt;Job을 생성하고 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;table id=&quot;2ff5cc73-c327-80ea-88ef-ff2b883fbdd6&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;연산&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-80ff-afba-e6ac6b669b43&quot;&gt;
&lt;td id=&quot;ws}w&quot;&gt;collect()&lt;/td&gt;
&lt;td id=&quot;=sQw&quot;&gt;모든 요소를 Driver로 가져옴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-80cd-9ccc-c9a0cd9f8362&quot;&gt;
&lt;td id=&quot;ws}w&quot;&gt;count()&lt;/td&gt;
&lt;td id=&quot;=sQw&quot;&gt;요소 개수 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-80d8-9777-e8d39923cc1d&quot;&gt;
&lt;td id=&quot;ws}w&quot;&gt;first()&lt;/td&gt;
&lt;td id=&quot;=sQw&quot;&gt;첫 번째 요소 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-8015-894c-d9e8bced46f4&quot;&gt;
&lt;td id=&quot;ws}w&quot;&gt;take(n)&lt;/td&gt;
&lt;td id=&quot;=sQw&quot;&gt;처음 n개 요소 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-8091-a878-f5b7ebd8b912&quot;&gt;
&lt;td id=&quot;ws}w&quot;&gt;reduce(f)&lt;/td&gt;
&lt;td id=&quot;=sQw&quot;&gt;모든 요소를 함수 f로 집계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-80c3-ba14-df3dbcc448cd&quot;&gt;
&lt;td id=&quot;ws}w&quot;&gt;foreach(f)&lt;/td&gt;
&lt;td id=&quot;=sQw&quot;&gt;각 요소에 함수 f 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2ff5cc73-c327-80f0-96df-d2dec1be536e&quot;&gt;
&lt;td id=&quot;ws}w&quot;&gt;saveAsTextFile(path)&lt;/td&gt;
&lt;td id=&quot;=sQw&quot;&gt;텍스트 파일로 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Transformation VS Action&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Transformation(Lazy)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Action(실행 트리거)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;map&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;show&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;filter&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;collect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;select&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;count&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cache / Persist&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cache와 Persist는 Spark에서 중간 연산 결과를 메모리 또는 디스크에 저장하여 재사용함으로써 반복 연산을 방지하고 성능을 향상시키는 기능이며, Cache는 &lt;b&gt;연산 결과를 메모리에 저장해서 재사용하는 기능&lt;/b&gt; 을하며 Persist는 &lt;b&gt;다양한 저장 옵션을 제공하는 확장된 개념&lt;/b&gt;입니다&lt;/p&gt;
&lt;pre id=&quot;code_1774618183421&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val rdd = sc.textFile(&quot;data/large_file.txt&quot;)
             .flatMap(_.split(&quot; &quot;))
             .map(w =&amp;gt; (w, 1))
             .reduceByKey(_ + _)

// 캐싱: 이후 Action에서 재계산 없이 메모리에서 읽음
rdd.cache()        // = persist(StorageLevel.MEMORY_ONLY)

rdd.count()        // 첫 번째 Action: 계산 후 메모리에 저장
rdd.take(10)       // 두 번째 Action: 메모리에서 바로 읽음 (빠름!)

// 캐시 해제
rdd.unpersist()&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;저장 수준&lt;/h4&gt;
&lt;table id=&quot;3005cc73-c327-80f2-9eee-ede88cff35d3&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Storage Level&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-805c-b68c-d303e15feb9c&quot;&gt;
&lt;td id=&quot;s;eB&quot;&gt;MEMORY_ONLY&lt;/td&gt;
&lt;td id=&quot;YprK&quot;&gt;메모리에만 저장 (기본값, cache())&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-8088-a625-f6758d17afc0&quot;&gt;
&lt;td id=&quot;s;eB&quot;&gt;MEMORY_AND_DISK&lt;/td&gt;
&lt;td id=&quot;YprK&quot;&gt;메모리 부족 시 디스크에 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-808b-bb19-c2a8a3187f27&quot;&gt;
&lt;td id=&quot;s;eB&quot;&gt;DISK_ONLY&lt;/td&gt;
&lt;td id=&quot;YprK&quot;&gt;디스크에만 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-80b6-9c79-c6c276470725&quot;&gt;
&lt;td id=&quot;s;eB&quot;&gt;MEMORY_ONLY_SER&lt;/td&gt;
&lt;td id=&quot;YprK&quot;&gt;직렬화하여 메모리 절약&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제 캐싱하는가?: 같은 RDD에 여러 Action을 호출하거나 반복 알고리즘(ML 등)에서 사용할 때.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Lineage (계보)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDD는 자신이 어떤 RDD에서 어떤 Transformation을 거쳐 만들어졌는지 기록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1774618269486&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;textFile(&quot;file.txt&quot;)  &amp;rarr;  flatMap(split)  &amp;rarr;  map((_,1))  &amp;rarr;  reduceByKey(+)
       RDD_0                 RDD_1             RDD_2           RDD_3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lineage의 역할&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장애 복구: 노드 장애로 파티션이 유실되면 Lineage를 따라 해당 파티션만 재계산&lt;/li&gt;
&lt;li&gt;재계산: 디스크에 저장하지 않으므로 체크포인트 없이도 복구 가능&lt;/li&gt;
&lt;li&gt;rdd.toDebugString 으로 Lineage 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Data Skew = Partition에 데이터가 과도하게 몰리는 현상&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 Partition에 데이터가 집중되어 일부 Task만 오래 실행되는 현상으로, 병렬 처리 효율을 떨어뜨립니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;병렬 처리 붕괴 1개 Task만 오래 걸림 &amp;rarr; 전체 성능 = 가장 느린 Task&lt;/li&gt;
&lt;li&gt;OOM (Out Of Memory)특정 Partition이 너무 큼 ,메모리 초과 ,executor 죽음&lt;/li&gt;
&lt;li&gt;Shuffle 병목&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tJbXJ/dJMb99Z8g8V/FhWqn83H5VkTFMDFFfCYOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tJbXJ/dJMb99Z8g8V/FhWqn83H5VkTFMDFFfCYOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tJbXJ/dJMb99Z8g8V/FhWqn83H5VkTFMDFFfCYOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtJbXJ%2FdJMb99Z8g8V%2FFhWqn83H5VkTFMDFFfCYOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;543&quot; height=&quot;301&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Data Skew 발생시점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;join&lt;/li&gt;
&lt;li&gt;groupby/ aggregation&lt;/li&gt;
&lt;li&gt;데이터 불균형&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Data Skew를 언제 의심해야하나?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1671&quot; data-start=&quot;1655&quot; data-section-id=&quot;msyq59&quot;&gt;특정 Task만 오래 걸림&lt;/li&gt;
&lt;li data-end=&quot;1693&quot; data-start=&quot;1672&quot; data-section-id=&quot;tjtzcp&quot;&gt;Stage에서 일부 Task만 남음&lt;/li&gt;
&lt;li data-end=&quot;1714&quot; data-start=&quot;1694&quot; data-section-id=&quot;1jdq5z2&quot;&gt;executor 하나만 계속 일함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Shuffle&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shuffle은 Spark에서 데이터를 key 기준으로 재분배하기 위해 Partition 간에 네트워크로 이동시키는 과정으로, 디스크 I/O와 네트워크 비용이 발생하여 성능에 큰 영향을 미치는 연산입니다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;542&quot; data-start=&quot;517&quot; data-section-id=&quot;1eaoj30&quot;&gt;기존 Partition에 데이터 있음&lt;/li&gt;
&lt;li data-end=&quot;565&quot; data-start=&quot;543&quot; data-section-id=&quot;13elqqy&quot;&gt;key 기준으로 다시 나눠야 함&lt;/li&gt;
&lt;li data-end=&quot;584&quot; data-start=&quot;566&quot; data-section-id=&quot;1cv4879&quot;&gt;다른 노드로 데이터 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774575289202&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stage 1 &amp;rarr; Shuffle &amp;rarr; Stage 2&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Shuffle 발생시점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 기준(key)이 바뀔 때&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;456&quot; data-start=&quot;402&quot;&gt;같은 key끼리 모아야 함&lt;/li&gt;
&lt;li data-end=&quot;456&quot; data-start=&quot;402&quot;&gt;데이터 이동 필요&lt;/li&gt;
&lt;li data-end=&quot;456&quot; data-start=&quot;402&quot;&gt;Shuffle 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/omyqW/dJMcai3KKLd/bBkIXtsrOfSxc2wgIKyVcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/omyqW/dJMcai3KKLd/bBkIXtsrOfSxc2wgIKyVcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/omyqW/dJMcai3KKLd/bBkIXtsrOfSxc2wgIKyVcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FomyqW%2FdJMcai3KKLd%2FbBkIXtsrOfSxc2wgIKyVcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;470&quot; height=&quot;360&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Shuffle&lt;span&gt;&amp;nbsp;상세&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shuffle은 Wide Transformation에서 발생하는 데이터 재분배 과정이다.&lt;/p&gt;
&lt;pre id=&quot;code_1774611599995&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;         Map Side                         Reduce Side
   ┌──────────────────┐              ┌──────────────────┐
   │ Executor 1       │              │ Executor 1       │
   │ Partition 0      │──── key A ──▶│ Partition 0      │
   │ [A:1, B:2, A:3]  │──── key B ──▶│ [A:1, A:3, A:2]  │
   └──────────────────┘              └──────────────────┘
   ┌──────────────────┐              ┌──────────────────┐
   │ Executor 2       │              │ Executor 2       │
   │ Partition 1      │──── key A ──▶│ Partition 1      │
   │ [A:2, C:1, B:1]  │──── key C ──▶│ [B:2, B:1, C:1]  │
   └──────────────────┘              └──────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shuffle이 비용이 큰 이유:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터를 로컬 디스크에 기록 (Map Side)&lt;/li&gt;
&lt;li&gt;네트워크를 통해 다른 Executor로 전송&lt;/li&gt;
&lt;li&gt;수신 측에서 다시 정렬/병합 (Reduce Side)&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Narrow Transformation&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 이동 없이 같은 Partition 안에서 끝나는 연산&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Shuffle 없음&lt;/li&gt;
&lt;li&gt;하나의 부모 파티션 &amp;rarr; 하나의 자식 파티션&lt;/li&gt;
&lt;li&gt;메모리 내 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774618336983&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Parent RDD          Child RDD
┌──────────┐        ┌──────────┐
│Partition1│ ─────&amp;rarr; │Partition1│
└──────────┘        └──────────┘
┌──────────┐        ┌──────────┐
│Partition2│ ─────&amp;rarr; │Partition2│
└──────────┘        └──────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 125722.png&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GlpZP/dJMcahw7dPX/kBfGKyshYVM748mcoiTwVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GlpZP/dJMcahw7dPX/kBfGKyshYVM748mcoiTwVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GlpZP/dJMcahw7dPX/kBfGKyshYVM748mcoiTwVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGlpZP%2FdJMcahw7dPX%2FkBfGKyshYVM748mcoiTwVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;595&quot; height=&quot;544&quot; data-filename=&quot;화면 캡처 2026-03-30 125722.png&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Wide Transformation&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 재분배(Shuffle)해야 수행되는 연산&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Shuffle 발생&lt;/li&gt;
&lt;li&gt;여러 부모 파티션 &amp;rarr; 하나의 자식 파티션&lt;/li&gt;
&lt;li&gt;성능 병목 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774618344554&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Parent RDD          Child RDD
┌──────────┐   ╲    ┌──────────┐
│Partition1│ ───╲──&amp;rarr;│Partition1│
└──────────┘    ╳   └──────────┘
┌──────────┐   ╱    ┌──────────┐
│Partition2│ ───╱──&amp;rarr;│Partition2│
└──────────┘        └──────────┘&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;네트워크 I/O 발생&lt;/li&gt;
&lt;li&gt;디스크 I/O 발생 (중간 결과 저장)&lt;/li&gt;
&lt;li&gt;성능 병목의 주요 원인&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-30 130028.png&quot; data-origin-width=&quot;769&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Cd6Io/dJMcaaLv626/gPguxk7mo8G9H3YQAMXIF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Cd6Io/dJMcaaLv626/gPguxk7mo8G9H3YQAMXIF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Cd6Io/dJMcaaLv626/gPguxk7mo8G9H3YQAMXIF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCd6Io%2FdJMcaaLv626%2FgPguxk7mo8G9H3YQAMXIF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;769&quot; height=&quot;525&quot; data-filename=&quot;화면 캡처 2026-03-30 130028.png&quot; data-origin-width=&quot;769&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Narrow vs Wide&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1621&quot; data-start=&quot;1468&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.093%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%;&quot;&gt;Nerrow&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%;&quot;&gt;Wide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1536&quot; data-start=&quot;1516&quot;&gt;
&lt;td style=&quot;width: 32.093%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1525&quot; data-start=&quot;1516&quot;&gt;데이터 이동&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1530&quot; data-start=&quot;1525&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1536&quot; data-start=&quot;1530&quot;&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1558&quot; data-start=&quot;1537&quot;&gt;
&lt;td style=&quot;width: 32.093%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1547&quot; data-start=&quot;1537&quot;&gt;Shuffle&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%;&quot; data-end=&quot;1552&quot; data-start=&quot;1547&quot; data-col-size=&quot;sm&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%;&quot; data-end=&quot;1558&quot; data-start=&quot;1552&quot; data-col-size=&quot;sm&quot;&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1583&quot; data-start=&quot;1559&quot;&gt;
&lt;td style=&quot;width: 32.093%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1571&quot; data-start=&quot;1559&quot;&gt;Partition&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1576&quot; data-start=&quot;1571&quot;&gt;유지&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1583&quot; data-start=&quot;1576&quot;&gt;재구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1600&quot; data-start=&quot;1584&quot;&gt;
&lt;td style=&quot;width: 32.093%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1589&quot; data-start=&quot;1584&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%;&quot; data-end=&quot;1594&quot; data-start=&quot;1589&quot; data-col-size=&quot;sm&quot;&gt;빠름&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%;&quot; data-end=&quot;1600&quot; data-start=&quot;1594&quot; data-col-size=&quot;sm&quot;&gt;느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1621&quot; data-start=&quot;1601&quot;&gt;
&lt;td style=&quot;width: 32.093%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1609&quot; data-start=&quot;1601&quot;&gt;Stage&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1614&quot; data-start=&quot;1609&quot;&gt;유지&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1621&quot; data-start=&quot;1614&quot;&gt;분리됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wide Transformation은 데이터를 Partition 간 재분배(Shuffle)하여 수행되는 연산으로, Spark에서 가장 큰 성능 비용을 유발한다&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Shuffle의 내부동작&lt;/h4&gt;
&lt;pre id=&quot;code_1775027981551&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Map Stage]
   &amp;darr;
Sort (메모리 정렬)
   &amp;darr;
Spill (디스크로 떨어짐)
   &amp;darr;
Shuffle 파일 생성
   &amp;darr;
[Reduce Stage]
   &amp;darr;
Merge (여러 파일 합침)
   &amp;darr;
최종 처리&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Sort (정렬)&lt;/h4&gt;
&lt;p data-end=&quot;375&quot; data-start=&quot;359&quot; data-ke-size=&quot;size16&quot;&gt;  Map Task에서 발생&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;416&quot; data-start=&quot;377&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;395&quot; data-start=&quot;377&quot; data-section-id=&quot;13x6chq&quot;&gt;데이터를 Key 기준으로 정렬&lt;/li&gt;
&lt;li data-end=&quot;416&quot; data-start=&quot;396&quot; data-section-id=&quot;8rolbi&quot;&gt;같은 key끼리 모으기 위한 준비&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1775028051663&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(key1, value)
(key2, value)
(key1, value)
&amp;darr;
정렬됨
(key1, value)
(key1, value)
(key2, value)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유 : Reduce 단계에서 빠르게 처리하기 위해서이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Spill (디스크로 내림)&lt;/h4&gt;
&lt;p data-end=&quot;596&quot; data-start=&quot;581&quot; data-ke-size=&quot;size16&quot;&gt;메모리가 부족하면 발생&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;619&quot; data-start=&quot;598&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;619&quot; data-start=&quot;598&quot; data-section-id=&quot;1kype9g&quot;&gt;정렬된 데이터를 디스크에 임시 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1775028090626&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;메모리 초과
&amp;darr;
spill file 1 생성
spill file 2 생성
...&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Shuffle Write&lt;/h4&gt;
&lt;p data-end=&quot;759&quot; data-start=&quot;742&quot; data-ke-size=&quot;size16&quot;&gt;Map Stage 끝날 때&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;785&quot; data-start=&quot;761&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;785&quot; data-start=&quot;761&quot; data-section-id=&quot;16but0n&quot;&gt;Partition별로 데이터 나눠서 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1775028123663&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Partition 1 &amp;rarr; Node A
Partition 2 &amp;rarr; Node B&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크로 전송 준비&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Shuffle Read (네트워크)&lt;/h4&gt;
&lt;p data-end=&quot;903&quot; data-start=&quot;885&quot; data-ke-size=&quot;size16&quot;&gt;Reduce Stage 시작&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;922&quot; data-start=&quot;905&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;922&quot; data-start=&quot;905&quot; data-section-id=&quot;8sniro&quot;&gt;다른 노드에서 데이터 가져옴&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;940&quot; data-start=&quot;924&quot; data-ke-size=&quot;size16&quot;&gt;여기서 병목 많이 발생:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;959&quot; data-start=&quot;941&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;950&quot; data-start=&quot;941&quot; data-section-id=&quot;n78pcp&quot;&gt;네트워크 비용&lt;/li&gt;
&lt;li data-end=&quot;959&quot; data-start=&quot;951&quot; data-section-id=&quot;yf9s9s&quot;&gt;데이터 크기&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Merge (병합)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 Spill 파일을 하나로 합침&lt;/p&gt;
&lt;pre id=&quot;code_1775028214696&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spill file 1
spill file 2
spill file 3
&amp;darr;
merge
&amp;darr;
하나의 정렬된 데이터&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1083&quot; data-start=&quot;1077&quot; data-ke-size=&quot;size16&quot;&gt;특징:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1109&quot; data-start=&quot;1084&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1094&quot; data-start=&quot;1084&quot; data-section-id=&quot;1lgihc9&quot;&gt;다시 정렬 유지&lt;/li&gt;
&lt;li data-end=&quot;1109&quot; data-start=&quot;1095&quot; data-section-id=&quot;1ve18f0&quot;&gt;CPU + 디스크 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Reduce 처리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1175&quot; data-start=&quot;1145&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1154&quot; data-start=&quot;1145&quot; data-section-id=&quot;sb1hho&quot;&gt;groupBy&lt;/li&gt;
&lt;li data-end=&quot;1161&quot; data-start=&quot;1155&quot; data-section-id=&quot;1j391fu&quot;&gt;join&lt;/li&gt;
&lt;li data-end=&quot;1175&quot; data-start=&quot;1162&quot; data-section-id=&quot;12dl6ck&quot;&gt;aggregation&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Spark SQL vs DataFrame vs RDD&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;451&quot; data-start=&quot;127&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.1163%;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 30.6977%;&quot;&gt;RDD&lt;/td&gt;
&lt;td style=&quot;width: 29.5349%;&quot;&gt;DataFrame&lt;/td&gt;
&lt;td style=&quot;width: 24.6512%;&quot;&gt;Spark SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;236&quot; data-start=&quot;203&quot;&gt;
&lt;td style=&quot;width: 15.1163%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;212&quot; data-start=&quot;203&quot;&gt;추상화 수준&lt;/td&gt;
&lt;td style=&quot;width: 30.6977%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;225&quot; data-start=&quot;212&quot;&gt;낮음 (로우 레벨)&lt;/td&gt;
&lt;td style=&quot;width: 29.5349%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;230&quot; data-start=&quot;225&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;width: 24.6512%;&quot; data-end=&quot;236&quot; data-start=&quot;230&quot; data-col-size=&quot;sm&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;306&quot; data-start=&quot;237&quot;&gt;
&lt;td style=&quot;width: 15.1163%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;246&quot; data-start=&quot;237&quot;&gt;데이터 구조&lt;/td&gt;
&lt;td style=&quot;width: 30.6977%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;271&quot; data-start=&quot;246&quot;&gt;객체 (Java/Scala/Python)&lt;/td&gt;
&lt;td style=&quot;width: 29.5349%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;295&quot; data-start=&quot;271&quot;&gt;테이블 형태 (Row + Schema)&lt;/td&gt;
&lt;td style=&quot;width: 24.6512%;&quot; data-end=&quot;306&quot; data-start=&quot;295&quot; data-col-size=&quot;sm&quot;&gt;SQL 테이블&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;350&quot; data-start=&quot;307&quot;&gt;
&lt;td style=&quot;width: 15.1163%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;316&quot; data-start=&quot;307&quot;&gt;타입 안정성&lt;/td&gt;
&lt;td style=&quot;width: 30.6977%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;329&quot; data-start=&quot;316&quot;&gt;O (컴파일 타임)&lt;/td&gt;
&lt;td style=&quot;width: 29.5349%;&quot; data-end=&quot;345&quot; data-start=&quot;329&quot; data-col-size=&quot;sm&quot;&gt;△ (언어에 따라 다름)&lt;/td&gt;
&lt;td style=&quot;width: 24.6512%;&quot; data-end=&quot;350&quot; data-start=&quot;345&quot; data-col-size=&quot;sm&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;402&quot; data-start=&quot;351&quot;&gt;
&lt;td style=&quot;width: 15.1163%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;357&quot; data-start=&quot;351&quot;&gt;최적화&lt;/td&gt;
&lt;td style=&quot;width: 30.6977%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;371&quot; data-start=&quot;357&quot;&gt;없음 (수동 최적화)&lt;/td&gt;
&lt;td style=&quot;width: 29.5349%;&quot; data-end=&quot;386&quot; data-start=&quot;371&quot; data-col-size=&quot;sm&quot;&gt;Catalyst 최적화&lt;/td&gt;
&lt;td style=&quot;width: 24.6512%;&quot; data-end=&quot;402&quot; data-start=&quot;386&quot; data-col-size=&quot;sm&quot;&gt;Catalyst 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;429&quot; data-start=&quot;403&quot;&gt;
&lt;td style=&quot;width: 15.1163%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;412&quot; data-start=&quot;403&quot;&gt;사용 난이도&lt;/td&gt;
&lt;td style=&quot;width: 30.6977%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;418&quot; data-start=&quot;412&quot;&gt;어려움&lt;/td&gt;
&lt;td style=&quot;width: 29.5349%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;423&quot; data-start=&quot;418&quot;&gt;보통&lt;/td&gt;
&lt;td style=&quot;width: 24.6512%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;429&quot; data-start=&quot;423&quot;&gt;쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;451&quot; data-start=&quot;430&quot;&gt;
&lt;td style=&quot;width: 15.1163%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;435&quot; data-start=&quot;430&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;width: 30.6977%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;440&quot; data-start=&quot;435&quot;&gt;느림&lt;/td&gt;
&lt;td style=&quot;width: 29.5349%;&quot; data-end=&quot;445&quot; data-start=&quot;440&quot; data-col-size=&quot;sm&quot;&gt;빠름&lt;/td&gt;
&lt;td style=&quot;width: 24.6512%;&quot; data-end=&quot;451&quot; data-start=&quot;445&quot; data-col-size=&quot;sm&quot;&gt;빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;-spark-sql이-편리한-이유&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Spark SQL이 편리한 이유&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용하는 데이터 포맷이 parquet이고, SQL만으로 처리할 수 있는 경우 schema에 매핑되는 클래스를 정의할 필요가 없다.&lt;/li&gt;
&lt;li&gt;Spark SQL에서는 Catalyst Optimizer가 최적화를 대신해준다.&lt;/li&gt;
&lt;li&gt;DataFrame은 Untyped Data인 Row를 사용하기 때문에 연산에 제한이 있었지만, Dataset은 Typed Data로 변환하여 처리하기 때문에 DataFrame보다 좀 더 복잡한 연산이 가능하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DataFrame이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataFrame은 스키마(컬럼 이름과 타입)가 있는 분산 데이터 컬렉션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계형 데이터베이스의 테이블이나 Pandas DataFrame과 유사하다.&lt;/p&gt;
&lt;pre id=&quot;code_1774618409084&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                        DataFrame                                │
│                                                                 │
│  ┌──────────┬──────────┬──────────┐                             │
│  │   name   │   age    │   city   │  &amp;larr; 스키마 (컬럼 정의)           │
│  │ (String) │ (Int)    │ (String) │                             │
│  ├──────────┼──────────┼──────────┤                             │
│  │  Alice   │   30     │  Seoul   │  &amp;larr; Row 1                    │
│  │   Bob    │   25     │  Busan   │  &amp;larr; Row 2                    │
│  │ Charlie  │   35     │  Seoul   │  &amp;larr; Row 3                    │
│  └──────────┴──────────┴──────────┘                             │
│                                                                 │
│  파티션 1: [Alice, Bob]     파티션 2: [Charlie]                    
└─────────────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Spark SQL vs Trino&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;Apache Spark&lt;/span&gt;&lt;/span&gt; SQL = 데이터 &amp;ldquo;처리(가공)&amp;rdquo; 엔진&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;Trino&lt;/span&gt;&lt;/span&gt; = 데이터 &amp;ldquo;조회(질의)&amp;rdquo; 엔진&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;415&quot; data-start=&quot;242&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;Spark SQL&lt;/td&gt;
&lt;td&gt;Trino&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;327&quot; data-start=&quot;298&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;303&quot; data-start=&quot;298&quot;&gt;역할&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;318&quot; data-start=&quot;303&quot;&gt;ETL / 데이터 처리&lt;/td&gt;
&lt;td data-end=&quot;327&quot; data-start=&quot;318&quot; data-col-size=&quot;sm&quot;&gt;쿼리 엔진&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;370&quot; data-start=&quot;328&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;336&quot; data-start=&quot;328&quot;&gt;실행 방식&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;347&quot; data-start=&quot;336&quot;&gt;Batch 중심&lt;/td&gt;
&lt;td data-end=&quot;370&quot; data-start=&quot;347&quot; data-col-size=&quot;sm&quot;&gt;Interactive (빠른 조회)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;390&quot; data-start=&quot;371&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;379&quot; data-start=&quot;371&quot;&gt;결과 저장&lt;/td&gt;
&lt;td data-end=&quot;384&quot; data-start=&quot;379&quot; data-col-size=&quot;sm&quot;&gt;가능&lt;/td&gt;
&lt;td data-end=&quot;390&quot; data-start=&quot;384&quot; data-col-size=&quot;sm&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;415&quot; data-start=&quot;391&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;396&quot; data-start=&quot;391&quot;&gt;목적&lt;/td&gt;
&lt;td data-end=&quot;405&quot; data-start=&quot;396&quot; data-col-size=&quot;sm&quot;&gt;데이터 생성&lt;/td&gt;
&lt;td data-end=&quot;415&quot; data-start=&quot;405&quot; data-col-size=&quot;sm&quot;&gt;데이터 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실무 사용 예시&lt;/h4&gt;
&lt;table id=&quot;3005cc73-c327-80a6-963d-cdc9ef7760e3&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 48.6047%;&quot;&gt;상황&lt;/td&gt;
&lt;td style=&quot;width: 51.2791%;&quot;&gt;엔진&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-801a-8038-d7c37b8cf891&quot;&gt;
&lt;td id=&quot;Z\Hk&quot; style=&quot;width: 48.6047%;&quot;&gt;&quot;어제 들어온 샘플 전부 재처리해줘&quot;&lt;/td&gt;
&lt;td id=&quot;ZCRl&quot; style=&quot;width: 51.2791%;&quot;&gt;Spark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-80e4-a61c-f786d7357e9e&quot;&gt;
&lt;td id=&quot;Z\Hk&quot; style=&quot;width: 48.6047%;&quot;&gt;&quot;breast 조직 샘플 몇 개야?&quot;&lt;/td&gt;
&lt;td id=&quot;ZCRl&quot; style=&quot;width: 51.2791%;&quot;&gt;Trino&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-8073-8011-ecebdee2def3&quot;&gt;
&lt;td id=&quot;Z\Hk&quot; style=&quot;width: 48.6047%;&quot;&gt;&quot;대시보드에 샘플 통계 연결해줘&quot;&lt;/td&gt;
&lt;td id=&quot;ZCRl&quot; style=&quot;width: 51.2791%;&quot;&gt;Trino&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-8037-9716-eaaecc69e25e&quot;&gt;
&lt;td id=&quot;Z\Hk&quot; style=&quot;width: 48.6047%;&quot;&gt;&quot;expression matrix Parquet로 변환해줘&quot;&lt;/td&gt;
&lt;td id=&quot;ZCRl&quot; style=&quot;width: 51.2791%;&quot;&gt;Spark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-8062-989b-c5c61a50f33a&quot;&gt;
&lt;td id=&quot;Z\Hk&quot; style=&quot;width: 48.6047%;&quot;&gt;&quot;지난달 데이터로 클러스터링 돌려줘&quot;&lt;/td&gt;
&lt;td id=&quot;ZCRl&quot; style=&quot;width: 51.2791%;&quot;&gt;Spark&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RDD vs DataFrame&lt;/h4&gt;
&lt;table id=&quot;3005cc73-c327-8036-9413-e6af6d1f00e7&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;특성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;RDD&lt;/td&gt;
&lt;td&gt;DataFrame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-80d0-be2f-d31904c44b22&quot;&gt;
&lt;td id=&quot;&amp;gt;VyJ&quot;&gt;&lt;b&gt;스키마&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;rrNx&quot;&gt;없음 (타입만 있음)&lt;/td&gt;
&lt;td id=&quot;wNAi&quot;&gt;있음 (컬럼 이름 + 타입)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-8012-b25d-e47156945ec9&quot;&gt;
&lt;td id=&quot;&amp;gt;VyJ&quot;&gt;&lt;b&gt;최적화&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;rrNx&quot;&gt;없음&lt;/td&gt;
&lt;td id=&quot;wNAi&quot;&gt;Catalyst Optimizer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-801e-9fd4-c7de84fac83e&quot;&gt;
&lt;td id=&quot;&amp;gt;VyJ&quot;&gt;&lt;b&gt;API&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;rrNx&quot;&gt;저수준 (map, reduce)&lt;/td&gt;
&lt;td id=&quot;wNAi&quot;&gt;고수준 (select, filter, join)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-80b1-a1c3-d394c69b6a98&quot;&gt;
&lt;td id=&quot;&amp;gt;VyJ&quot;&gt;&lt;b&gt;성능&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;rrNx&quot;&gt;직접 최적화 필요&lt;/td&gt;
&lt;td id=&quot;wNAi&quot;&gt;자동 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-804e-b795-d61de91b2dab&quot;&gt;
&lt;td id=&quot;&amp;gt;VyJ&quot;&gt;&lt;b&gt;언어 간 성능&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;rrNx&quot;&gt;Java/Scala만 빠름&lt;/td&gt;
&lt;td id=&quot;wNAi&quot;&gt;Python도 빠름 (JVM에서 실행)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-80c5-9c63-cfd064210f46&quot;&gt;
&lt;td id=&quot;&amp;gt;VyJ&quot;&gt;&lt;b&gt;사용 용도&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;rrNx&quot;&gt;비정형 데이터, 세밀한 제어&lt;/td&gt;
&lt;td id=&quot;wNAi&quot;&gt;구조화된 데이터, 일반적인 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 DataFrame이 더 빠른가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataFrame은 Catalyst Optimizer를 통해&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;논리적 실행 계획 생성&lt;/li&gt;
&lt;li&gt;규칙 기반 최적화 (불필요한 연산 제거, 조건 푸시다운)&lt;/li&gt;
&lt;li&gt;비용 기반 최적화 (최적의 조인 전략 선택)&lt;/li&gt;
&lt;li&gt;코드 생성 (Java 바이트코드 동적 생성)&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;데이터 포맷 읽기/쓰기&lt;/h4&gt;
&lt;table id=&quot;3005cc73-c327-80b8-b812-c67ad20491c6&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;포맷&lt;/td&gt;
&lt;td&gt;특징&lt;/td&gt;
&lt;td&gt;용도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-80cf-95a5-fc9413c62dbc&quot;&gt;
&lt;td id=&quot;hKpR&quot;&gt;&lt;b&gt;Parquet&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;OiHD&quot;&gt;컬럼 기반, 압축, 스키마 포함&lt;/td&gt;
&lt;td id=&quot;Wwgm&quot;&gt;분석용 저장 (권장)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-8033-8cc1-fea17c5a9516&quot;&gt;
&lt;td id=&quot;hKpR&quot;&gt;&lt;b&gt;CSV&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;OiHD&quot;&gt;텍스트, 호환성 높음&lt;/td&gt;
&lt;td id=&quot;Wwgm&quot;&gt;데이터 교환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-80cb-b3cf-fabc7904b84e&quot;&gt;
&lt;td id=&quot;hKpR&quot;&gt;&lt;b&gt;JSON&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;OiHD&quot;&gt;중첩 구조 지원&lt;/td&gt;
&lt;td id=&quot;Wwgm&quot;&gt;API 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3005cc73-c327-8004-bdaa-d27bb825f7e4&quot;&gt;
&lt;td id=&quot;hKpR&quot;&gt;&lt;b&gt;ORC&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;OiHD&quot;&gt;Hive 최적화 컬럼 포맷&lt;/td&gt;
&lt;td id=&quot;Wwgm&quot;&gt;Hive 환경&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spark</category>
      <category>DataSkew</category>
      <category>LazyEvalution</category>
      <category>narrow</category>
      <category>spark</category>
      <category>sparkcontext</category>
      <category>Suffle</category>
      <category>Wide</category>
      <category>데이터엔지니어</category>
      <category>스파크</category>
      <category>스파크이론</category>
      <author>jyu_seo_</author>
      <guid isPermaLink="true">https://jyu-seo.tistory.com/181</guid>
      <comments>https://jyu-seo.tistory.com/181#entry181comment</comments>
      <pubDate>Fri, 27 Mar 2026 11:37:51 +0900</pubDate>
    </item>
    <item>
      <title>[Spark] - 파티션 최적화</title>
      <link>https://jyu-seo.tistory.com/180</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 블로그에서는 파티셔닝을 최적화 하는 방법에 대해서 작성해보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티셔닝을 최적화 한다는게 어떤 뜻이냐면, Spark에서 데이터를 분산해서 파티션 단위로 저장하고 그것을 메모리 상이든 파일 단위에서 쓰고 있기를 할때 파티션 단위로 모든것을 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 파티션을 어떻게 만드냐에 따라서 Spark는 파티션된 모든 파일을 읽어오거나 처리할 필요없이 &lt;b&gt;특정 파티션만&lt;/b&gt; 다루면서 빠르게 효율적으로 Spark 연산을 극대화 시킬수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition01.png&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nszji/dJMcacvLhUI/AZBInurUqe1FAnVOdHPJGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nszji/dJMcacvLhUI/AZBInurUqe1FAnVOdHPJGK/img.png&quot; data-alt=&quot;sparkSession을 하나 만들고 appName을 optimizing partitions을 만들었습니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nszji/dJMcacvLhUI/AZBInurUqe1FAnVOdHPJGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnszji%2FdJMcacvLhUI%2FAZBInurUqe1FAnVOdHPJGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;775&quot; height=&quot;409&quot; data-filename=&quot;partition01.png&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;409&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;sparkSession을 하나 만들고 appName을 optimizing partitions을 만들었습니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition02.png&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rjbo5/dJMcaaEH5Xj/S1YV8N30kFqXtpi0ufNX50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rjbo5/dJMcaaEH5Xj/S1YV8N30kFqXtpi0ufNX50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rjbo5/dJMcaaEH5Xj/S1YV8N30kFqXtpi0ufNX50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frjbo5%2FdJMcaaEH5Xj%2FS1YV8N30kFqXtpi0ufNX50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;794&quot; height=&quot;521&quot; data-filename=&quot;partition02.png&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습을 위해서 단일 파티션말고 다중파티션을 만들기위해 여러개의 csv를 만들고, withColumn으로 각각의 어떤 행이 파티션 파일에 존재했는지 추적해나가는 과정입니다. 이걸 추적해나가는 과정을 사용하기위해 input_file_name을 import 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;split을 통해서 정리해나가는과정입니다. python이랑 동일하고 part 뒤에 인식해야될 것들만 정리하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition03.png&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KBhVh/dJMcacP2y9l/fGSUb3O76yZJq0TKmERjhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KBhVh/dJMcacP2y9l/fGSUb3O76yZJq0TKmERjhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KBhVh/dJMcacP2y9l/fGSUb3O76yZJq0TKmERjhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKBhVh%2FdJMcacP2y9l%2FfGSUb3O76yZJq0TKmERjhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;490&quot; data-filename=&quot;partition03.png&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;df.group을 불러오고 partition단위로 보고싶어서 part와 store_code를 가져오고, orderBy를 통해서 정렬해주는 모습입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition04.png&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o30OH/dJMcabXT0tB/uuW2oQ1MOqPcZKOO4ENSr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o30OH/dJMcabXT0tB/uuW2oQ1MOqPcZKOO4ENSr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o30OH/dJMcabXT0tB/uuW2oQ1MOqPcZKOO4ENSr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo30OH%2FdJMcabXT0tB%2FuuW2oQ1MOqPcZKOO4ENSr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1114&quot; height=&quot;734&quot; data-filename=&quot;partition04.png&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;databricks를 사용하지 않기 때문에 지금 로컬환경에서 시각화 하기위해서 pandas 라이브러리를 불러오고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pivot_table을 통해서 차트를 만들고 index 단위로 partition을 했습니다. columns는 store_code 기준으로 하고 value 값은 count를 설정해주었습니다 groupby를 해가지고 나온 값이니까 aggregate function 같은경우는 sum을 해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;plot을 사용해서 &lt;b&gt;가로형 막대차트&lt;/b&gt;를 만들어 주었습니다. stacked로 위치를 확인해본 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인해본 결과 partition에 점포의 데이터가 전체 데이터가 골고루 잘 섞여있는 모습을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고나서 이제 저장을 할건데요 file_path에 이름을 줘서 저장해보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition05.png&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxNBCt/dJMcaivU3Ub/em76797asC8i2DP9k8AUz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxNBCt/dJMcaivU3Ub/em76797asC8i2DP9k8AUz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxNBCt/dJMcaivU3Ub/em76797asC8i2DP9k8AUz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxNBCt%2FdJMcaivU3Ub%2Fem76797asC8i2DP9k8AUz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;851&quot; height=&quot;129&quot; data-filename=&quot;partition05.png&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;129&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;df_sorted.write모드로 해주고 overwite로 해서 혹시나 있으면 무시하고 덮어쓰는거고,format.csv로 파일을 저장하고 옵션을 통해서 header값을 명시하는것으로 저장하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition06.png&quot; data-origin-width=&quot;359&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k2bZk/dJMcagrmuok/IpyAf1392JSEuXX8eq3XS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k2bZk/dJMcagrmuok/IpyAf1392JSEuXX8eq3XS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k2bZk/dJMcagrmuok/IpyAf1392JSEuXX8eq3XS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk2bZk%2FdJMcagrmuok%2FIpyAf1392JSEuXX8eq3XS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;359&quot; height=&quot;419&quot; data-filename=&quot;partition06.png&quot; data-origin-width=&quot;359&quot; data-origin-height=&quot;419&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 dataframe view를 불러오는걸로해서 방금전에 저장했던 파일을 불러보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition07.png&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PG45Z/dJMcah4TmWT/y2k7kJlKDJBlQGC0LdLzz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PG45Z/dJMcah4TmWT/y2k7kJlKDJBlQGC0LdLzz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PG45Z/dJMcah4TmWT/y2k7kJlKDJBlQGC0LdLzz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPG45Z%2FdJMcah4TmWT%2Fy2k7kJlKDJBlQGC0LdLzz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;426&quot; data-filename=&quot;partition07.png&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 보면 new_file_path로 데이터를 불러왔습니다. 이번에는 분포도를 한번 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition08.png&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EFIBb/dJMcagrmuwF/FpYc6cbvipnwuC3dw86rvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EFIBb/dJMcagrmuwF/FpYc6cbvipnwuC3dw86rvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EFIBb/dJMcagrmuwF/FpYc6cbvipnwuC3dw86rvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEFIBb%2FdJMcagrmuwF%2FFpYc6cbvipnwuC3dw86rvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;392&quot; data-filename=&quot;partition08.png&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 가져온거기때문에 위에 코드를 그대로 가져와서 df_new_ group으로 확인해본 결과입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition09.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;513&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxAowq/dJMcaf63nCU/u6yDRQSg3JBEVjfcHcTpkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxAowq/dJMcaf63nCU/u6yDRQSg3JBEVjfcHcTpkk/img.png&quot; data-alt=&quot;이것도 위와비슷한 방법으로 분포도를 체크한 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxAowq/dJMcaf63nCU/u6yDRQSg3JBEVjfcHcTpkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxAowq%2FdJMcaf63nCU%2Fu6yDRQSg3JBEVjfcHcTpkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1088&quot; height=&quot;513&quot; data-filename=&quot;partition09.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;513&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이것도 위와비슷한 방법으로 분포도를 체크한 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약에 store_code에 A1 점포에 대한 데이터만 가져오고싶다 라고하면 어떻게 해볼수 있을까요?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럴때 파티션화 되어있는 데이터에서 A1 특정 점포에 대하여 불러오는 경우와 골고루 섞여있는 파티션 데이터 안에 A1~D2까지 점포들이 골고루 섞여있는경우에 데이터를 불러오는 경우 Spark내부에서 어떻게 동작하는지 고민할 필요가 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;A1 점포에 대해서 한번 불러와 보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition11.png&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8YuNl/dJMcaiipuyL/CBY3sMDIoga0Lfzdo7Hjo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8YuNl/dJMcaiipuyL/CBY3sMDIoga0Lfzdo7Hjo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8YuNl/dJMcaiipuyL/CBY3sMDIoga0Lfzdo7Hjo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8YuNl%2FdJMcaiipuyL%2FCBY3sMDIoga0Lfzdo7Hjo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;363&quot; data-filename=&quot;partition11.png&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;groupby를 해주고 agg에 expr을 해서 count해준 결과입니다. 실제로 partition 0에 대해서만 A1으로 해서 Count가 집계가 된걸 알수가 있습니다. 그러면 똑같이 명령을 복사해서 이번에는 원래의 file_path와 비교해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와같이 A1에서만 필터링한다고 하면 전문용어로 &lt;b&gt;predicate pushdown이라고 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;partition12.png&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p6pqc/dJMcad2qBcV/zdSRLoIRFOaNRsdmFgBdG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p6pqc/dJMcad2qBcV/zdSRLoIRFOaNRsdmFgBdG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p6pqc/dJMcad2qBcV/zdSRLoIRFOaNRsdmFgBdG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp6pqc%2FdJMcad2qBcV%2FzdSRLoIRFOaNRsdmFgBdG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;586&quot; height=&quot;494&quot; data-filename=&quot;partition12.png&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래같은경우에는 연산이 조금더 복잡하고 shuffle이 많이 발생하는 구조가 되기때문에 지금 데이터가 그렇게 많치않은 데이터이지만 데이터량이 방대하다고 가정했을때는 파티션이 잘되어있지 않게 되면 Spark의 연산의 퍼포먼스가 많이 떨어지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;databrick에서는 z-ordering이라고하는 이름을 줘서 해당 partitioning을 하는 기법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 델타 테이블에 적용되는 개념이고 이와같이 csv파일을 저장하는 개념에서는 z-ordering이라고 부르진 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;z-ordering과 같은 파티션 최적화의 개념이 무엇인지 이해하기위해서 한번 만들어 봤습니다&lt;/p&gt;</description>
      <category>Spark</category>
      <category>databrick</category>
      <category>partition</category>
      <category>spark</category>
      <category>z-ordering</category>
      <category>데이터엔지니어</category>
      <category>스파크</category>
      <author>jyu_seo_</author>
      <guid isPermaLink="true">https://jyu-seo.tistory.com/180</guid>
      <comments>https://jyu-seo.tistory.com/180#entry180comment</comments>
      <pubDate>Wed, 25 Mar 2026 18:21:31 +0900</pubDate>
    </item>
    <item>
      <title>[Spark] - Json 데이터 다루기(with_Explode)</title>
      <link>https://jyu-seo.tistory.com/179</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번블로그는 Json파일을 가져와서 데이터를 다루는 몇가지 방법을 알려드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;opensource json example 5를 가져와서 다뤄봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크에 들어가서 데이터를 긁은다음에 json으로 변환해서 업로드후 사용하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://opensource.adobe.com/Spry/samples/data_region/JSONDataSetSample.html#Example5&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://opensource.adobe.com/Spry/samples/data_region/JSONDataSetSample.html#Example5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;json01.png&quot; data-origin-width=&quot;685&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lllod/dJMcah4SmPb/SFPXKIZBqNUfItA9eDCdik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lllod/dJMcah4SmPb/SFPXKIZBqNUfItA9eDCdik/img.png&quot; data-alt=&quot;handling json data로 sparksession을 만들어줍니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lllod/dJMcah4SmPb/SFPXKIZBqNUfItA9eDCdik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flllod%2FdJMcah4SmPb%2FSFPXKIZBqNUfItA9eDCdik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;685&quot; height=&quot;405&quot; data-filename=&quot;json01.png&quot; data-origin-width=&quot;685&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;handling json data로 sparksession을 만들어줍니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;json02.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPwbjy/dJMcagSqY86/3LN2MOaRCS6KEIfdDYUGSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPwbjy/dJMcagSqY86/3LN2MOaRCS6KEIfdDYUGSk/img.png&quot; data-alt=&quot;json파일을 불러오고 컬럼이름을 id와 key값으로 변경하는모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPwbjy/dJMcagSqY86/3LN2MOaRCS6KEIfdDYUGSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPwbjy%2FdJMcagSqY86%2F3LN2MOaRCS6KEIfdDYUGSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;591&quot; data-filename=&quot;json02.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;json파일을 불러오고 컬럼이름을 id와 key값으로 변경하는모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Convert nested json&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;json03.png&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PBN3x/dJMcabwNyKD/4Mm75cbkIM2kT6fqbCF3dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PBN3x/dJMcabwNyKD/4Mm75cbkIM2kT6fqbCF3dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PBN3x/dJMcabwNyKD/4Mm75cbkIM2kT6fqbCF3dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPBN3x%2FdJMcabwNyKD%2F4Mm75cbkIM2kT6fqbCF3dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;935&quot; height=&quot;386&quot; data-filename=&quot;json03.png&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bat_df = raw_df랑 join을 시켜주고 key와 상단에 나와있는 batters &amp;gt; batter 데이터를 불러왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마를 통해서 확인해보니 저렇게 가로로 되어있어서 하단이미지처럼 explode를 사용해서 데이터를 일렬로 나눠주는 모습입니다.explode = 폭파란뜻이기도 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;json04.png&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LwgSG/dJMcahqgUdM/PUHOyNP5dRemk8Uy0kV9gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LwgSG/dJMcahqgUdM/PUHOyNP5dRemk8Uy0kV9gk/img.png&quot; data-alt=&quot;정리가 잘된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LwgSG/dJMcahqgUdM/PUHOyNP5dRemk8Uy0kV9gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLwgSG%2FdJMcahqgUdM%2FPUHOyNP5dRemk8Uy0kV9gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;699&quot; height=&quot;417&quot; data-filename=&quot;json04.png&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정리가 잘된 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;df_bat와 bat2df을 join하고 그리고 id컬럼을 *로 붙힌다음에 정렬한 모습입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;json05.png&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJbb1G/dJMcaaEG9mU/CA0rtFKQx5sNtTQthhDGU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJbb1G/dJMcaaEG9mU/CA0rtFKQx5sNtTQthhDGU1/img.png&quot; data-alt=&quot;2차정렬&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJbb1G/dJMcaaEG9mU/CA0rtFKQx5sNtTQthhDGU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJbb1G%2FdJMcaaEG9mU%2FCA0rtFKQx5sNtTQthhDGU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;275&quot; data-filename=&quot;json05.png&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2차정렬&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;json06.png&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WcLtr/dJMcaaLrAry/L5Xt8B4lxuIPsd7WBdi1eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WcLtr/dJMcaaLrAry/L5Xt8B4lxuIPsd7WBdi1eK/img.png&quot; data-alt=&quot;topping 도 가져와봤습니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WcLtr/dJMcaaLrAry/L5Xt8B4lxuIPsd7WBdi1eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWcLtr%2FdJMcaaLrAry%2FL5Xt8B4lxuIPsd7WBdi1eK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1216&quot; height=&quot;698&quot; data-filename=&quot;json06.png&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;topping 도 가져와봤습니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Join&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;json07.png&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzPH4H/dJMcaaEG9rB/brvPZx8Dh84tXR8Kf4L8T0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzPH4H/dJMcaaEG9rB/brvPZx8Dh84tXR8Kf4L8T0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzPH4H/dJMcaaEG9rB/brvPZx8Dh84tXR8Kf4L8T0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzPH4H%2FdJMcaaEG9rB%2FbrvPZx8Dh84tXR8Kf4L8T0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;795&quot; height=&quot;726&quot; data-filename=&quot;json07.png&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 가져온 bat와 topping을 id와 type을 갖고와서 join시키는 과정입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한게 json 파일을 가져와서 데이터를 다루는 작업을 해봤습니다&lt;/p&gt;</description>
      <category>Spark</category>
      <category>Convert nested json</category>
      <category>explode</category>
      <category>json</category>
      <category>spark</category>
      <category>데이터엔지니어</category>
      <category>스키마</category>
      <category>스파크</category>
      <author>jyu_seo_</author>
      <guid isPermaLink="true">https://jyu-seo.tistory.com/179</guid>
      <comments>https://jyu-seo.tistory.com/179#entry179comment</comments>
      <pubDate>Tue, 24 Mar 2026 22:45:46 +0900</pubDate>
    </item>
    <item>
      <title>[Spark] - Null값 처리</title>
      <link>https://jyu-seo.tistory.com/178</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Null&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Null값을 다루는 방법론에 대해서 여러가지 준비를 해봤습니다. 실제로 데이터를 불러오다 보면 데이터를 불러오는 과정에서 특정값에 어떤 오류가 있어서 Null값으로 처리되는 경우가 있거나 원래부터 데이터 값이 존재하지 않을 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Null값을 어떻게 처리하냐에 따라서 특정 쿼리를 날릴때 어떤 Null값이 존재하는것을 지울것인지 채워 넣을것인지 아니면 그대로 둘것인지 의사결정을 할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Null로 다루는 주제입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;window를 사용하여 데이터의 일정부분 null 생성&lt;/li&gt;
&lt;li&gt;null 값을 가진 row 단순 제거&lt;/li&gt;
&lt;li&gt;null 값을 가진 row 제거의 다양한 방법론&lt;/li&gt;
&lt;li&gt;null 값 단순 채우기&lt;/li&gt;
&lt;li&gt;fill forward 방식으로 null 값 채우기&lt;/li&gt;
&lt;li&gt;fill backward 방식으로 null 값 채우기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;null01.png&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l9hRv/dJMcadnOTm3/3eTdAFnflTzkkJftgI3Kck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l9hRv/dJMcadnOTm3/3eTdAFnflTzkkJftgI3Kck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l9hRv/dJMcadnOTm3/3eTdAFnflTzkkJftgI3Kck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl9hRv%2FdJMcadnOTm3%2F3eTdAFnflTzkkJftgI3Kck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;405&quot; data-filename=&quot;null01.png&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;null02.png&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd3WwI/dJMcac3wBdu/xbon8GrqWGPkSQR6B4XxeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd3WwI/dJMcac3wBdu/xbon8GrqWGPkSQR6B4XxeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd3WwI/dJMcac3wBdu/xbon8GrqWGPkSQR6B4XxeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd3WwI%2FdJMcac3wBdu%2Fxbon8GrqWGPkSQR6B4XxeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;816&quot; height=&quot;525&quot; data-filename=&quot;null02.png&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;window를 사용하여 데이터의 일정부분 null 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spark session을 만들어주고 &lt;b&gt;rand를 사용해서 난수 0과 1사이에 다양한 난수를 발생하는 rand를 명령어로&lt;/b&gt; 작성해서 추가 행 컬럼을 만들어 주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rand를 준이유는 윈도우를 줘서 행번호를 입력해서 특정한 비율만큼 Null값을 줄것이기 때문에 만들었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-24 164036.png&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GmSTy/dJMcaaYXmN0/40jJFNEGW4nRqZHLRTzAM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GmSTy/dJMcaaYXmN0/40jJFNEGW4nRqZHLRTzAM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GmSTy/dJMcaaYXmN0/40jJFNEGW4nRqZHLRTzAM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGmSTy%2FdJMcaaYXmN0%2F40jJFNEGW4nRqZHLRTzAM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;925&quot; height=&quot;726&quot; data-filename=&quot;화면 캡처 2026-03-24 164036.png&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;window방식을 통해서 균등하게 Null값이 존재할수 있도록 만들어봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터에 5%만 주입을 하고, sales_rev 에서 rows_to_nullify, 1080보다 작은 숫자는 null값을 주겠다라는 명령입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.otherwise를 사용해서 1080보다 크면 원래 매출 데이터를 그대로 쓴다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새롭게 난수를 주기위해서 withColumn('rand',rand())를 주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sales_qty에 같은 명령을 주었습니다. 그리고 마지막에 rand를 버려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-24 165407.png&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;473&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oAsTy/dJMcafMLY9S/KzaLVAbjcYlkN7mcvlAzA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oAsTy/dJMcafMLY9S/KzaLVAbjcYlkN7mcvlAzA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oAsTy/dJMcafMLY9S/KzaLVAbjcYlkN7mcvlAzA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoAsTy%2FdJMcafMLY9S%2FKzaLVAbjcYlkN7mcvlAzA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;735&quot; height=&quot;473&quot; data-filename=&quot;화면 캡처 2026-03-24 165407.png&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;473&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sales_rev와 sales_qty의 null값을 filter를 통해서 확인해볼수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;null 값을 가진 row 단순 제거&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-24 165742.png&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgkvSl/dJMcagky7bz/nFaKoUgPe8pazwNNe2Fvc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgkvSl/dJMcagky7bz/nFaKoUgPe8pazwNNe2Fvc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgkvSl/dJMcagky7bz/nFaKoUgPe8pazwNNe2Fvc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgkvSl%2FdJMcagky7bz%2FnFaKoUgPe8pazwNNe2Fvc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;552&quot; data-filename=&quot;화면 캡처 2026-03-24 165742.png&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 제거는 간단합니다. Null값을 가진 데이터프레임에 대하여 Null값을 찾아서 na drop을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;na = null&lt;/b&gt; 을 찾아서 제거시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에있는 값을가지고 df_clean에 대해서 null값이 지워졌는지 확인해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인 결과 21613개의 total data가 19500개로 null값이 지워진걸 확인이 됩니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;How argument&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-24 171709.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;571&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l33xC/dJMb996PDmg/12iNy3crzvKJOu9uJlhjk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l33xC/dJMb996PDmg/12iNy3crzvKJOu9uJlhjk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l33xC/dJMb996PDmg/12iNy3crzvKJOu9uJlhjk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl33xC%2FdJMb996PDmg%2F12iNy3crzvKJOu9uJlhjk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;571&quot; data-filename=&quot;화면 캡처 2026-03-24 171709.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;571&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 how방식을 사용해봤는데 how방식에는 any와 all방식이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;any는 어느하나라도 행기준으로 봤을때 null이 존재하게되면 탈락시키는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;all은 데이터가 모두 다 null값으로 존재하는 경우 탈락시키는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;all이라는 명령어를 사용하게되면 너무 과한 방식이기때문에 그렇다면 어떤 방식이 또 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;drop nulls on the basis of column&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-24 172300.png&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehEnsI/dJMcahcIomt/Xy6XkF0UBjEGkLnwGZqKcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehEnsI/dJMcahcIomt/Xy6XkF0UBjEGkLnwGZqKcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehEnsI/dJMcahcIomt/Xy6XkF0UBjEGkLnwGZqKcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FehEnsI%2FdJMcahcIomt%2FXy6XkF0UBjEGkLnwGZqKcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;854&quot; height=&quot;564&quot; data-filename=&quot;화면 캡처 2026-03-24 172300.png&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 컬럼에 조건에 걸어두는 방식입니다 subset을 이용해서 특정 컬럼을 지정해서 탈락시켜주는 과정입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 확인해보면 sales_rev와 sales_qty가 null이 0이 되어있는걸 확인해볼수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 null값을 어떻게 채우냐?&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;fill nulls&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-24 172907.png&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n9f9D/dJMcabct2l9/ewpVUBCemiHeJmPvS7ctt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n9f9D/dJMcabct2l9/ewpVUBCemiHeJmPvS7ctt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n9f9D/dJMcabct2l9/ewpVUBCemiHeJmPvS7ctt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn9f9D%2FdJMcabct2l9%2FewpVUBCemiHeJmPvS7ctt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;801&quot; height=&quot;572&quot; data-filename=&quot;화면 캡처 2026-03-24 172907.png&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 drop이 아니라 fill을 사용해서 sales_qty 와 sales_rev에 1씩 추가해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 되면 null값에 가진 컬럼에 대해서 1씩 변경되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이렇게 null값을 채우는게 맞나?라는 생각이 들것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 fill forward와 fill backward에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fill forward : 이전 행의 데이터로 다음 행을 값을 채우는 방법&lt;/li&gt;
&lt;li&gt;fill backward : 다음 행의 데이터로 이전 행을 값을 채우는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;fill forward&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-24 173349.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DkdXe/dJMcahw2uAY/kiQQKqDaN4foLWPFpBBjLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DkdXe/dJMcahw2uAY/kiQQKqDaN4foLWPFpBBjLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DkdXe/dJMcahw2uAY/kiQQKqDaN4foLWPFpBBjLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDkdXe%2FdJMcahw2uAY%2FkiQQKqDaN4foLWPFpBBjLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;748&quot; data-filename=&quot;화면 캡처 2026-03-24 173349.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;pandas에서도 forward fill하고 backward fill의 방식이 제공됩니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;같은 방식으로 spark에서도 어떤방식으로 존재하는지 작성해봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;window와 last(마지막 값)을 불러와서 rowsBetween으로 unboundedpreceding, currentRow를 사용해서&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이전 행의 데이터로 다음 행을 값을 채우는 방법을 사용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;'즉,윈도우 스팩을 통해서 스토어코드,프로덕트 코드에 따라서 시간 순서대로 시계율로 데이터가 정리된것을 가지고서 이와같이&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;직전의 값을 가지고 현재행을 바라보는 이와같은 rowsbetween 값을 한다음 마지막값에 ignore nulls로 해서 true값을 집어넣고 그다음에 직전의 값으로 채워넣는 형식입니다.'&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;backward fill&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-24 173513.png&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMXyv0/dJMcahDLOga/JjQkQzZJOGfckKZ4a8P2fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMXyv0/dJMcahDLOga/JjQkQzZJOGfckKZ4a8P2fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMXyv0/dJMcahDLOga/JjQkQzZJOGfckKZ4a8P2fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMXyv0%2FdJMcahDLOga%2FJjQkQzZJOGfckKZ4a8P2fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1056&quot; height=&quot;794&quot; data-filename=&quot;화면 캡처 2026-03-24 173513.png&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;backward fill은 rowsBetween은 위와 반대로 작성해서 코드작성 방식은 같습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;imputation by mean&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-03-24 174436.png&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sp8xI/dJMcai3IbiM/YqSKJD506aJqSdujWhOVf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sp8xI/dJMcai3IbiM/YqSKJD506aJqSdujWhOVf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sp8xI/dJMcai3IbiM/YqSKJD506aJqSdujWhOVf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSp8xI%2FdJMcai3IbiM%2FYqSKJD506aJqSdujWhOVf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1263&quot; height=&quot;794&quot; data-filename=&quot;화면 캡처 2026-03-24 174436.png&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null값을 채워넣는 과정중에서 평균값으로 null값을 채워넣는 방법중 하나인 imputation by mean입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null값을 채워넣는 것을 전문용어로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;imputation&lt;span&gt; 이라고 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spark</category>
      <category>backfill</category>
      <category>fillforward</category>
      <category>null</category>
      <category>spark</category>
      <category>데이터엔지니어</category>
      <category>스파크</category>
      <author>jyu_seo_</author>
      <guid isPermaLink="true">https://jyu-seo.tistory.com/178</guid>
      <comments>https://jyu-seo.tistory.com/178#entry178comment</comments>
      <pubDate>Tue, 24 Mar 2026 17:52:23 +0900</pubDate>
    </item>
    <item>
      <title>[Spark] - 정규식을 통한 문자열 처리</title>
      <link>https://jyu-seo.tistory.com/177</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규식 표현을 통해서 문자열 데이터를 처리하는 방법을 실습을 통해 블로그를 작성해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 3가지로 나눠서 명령문을 나눠봤는데요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;regexp_replace&lt;/li&gt;
&lt;li&gt;rlike&lt;/li&gt;
&lt;li&gt;regexp_extract&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RegularExpressionReplace, RegularRlike라고 하는 명령어와 RegularExpressionExtract 라고 하는 명령어가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어는 실습내용을 통해서 설명하도록하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 데이터를 다운받아서 csv를 가져와 실습하였습니다. 500redoce는 free이기 때문에 다운받아서 실습을 따라해보시는것도 추천드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.briandunning.com/sample-data/&quot;&gt;Free Sample Data&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774261346560&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Free Sample Data&quot; data-og-description=&quot;Always test your software with a &amp;quot;worst-case scenario&amp;quot; amount of sample data, to get an accurate sense of its performance in the real world. Files are provided as CSV. Included fields are: First Name, Last Name, Company, Address, City, County (where applic&quot; data-og-host=&quot;www.briandunning.com&quot; data-og-source-url=&quot;https://www.briandunning.com/sample-data/&quot; data-og-url=&quot;https://www.briandunning.com/sample-data/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.briandunning.com/sample-data/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.briandunning.com/sample-data/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Free Sample Data&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Always test your software with a &quot;worst-case scenario&quot; amount of sample data, to get an accurate sense of its performance in the real world. Files are provided as CSV. Included fields are: First Name, Last Name, Company, Address, City, County (where applic&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.briandunning.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;handling.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HV2WE/dJMcac3vJ56/9C1gldGGdQDydPf7WdGWf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HV2WE/dJMcac3vJ56/9C1gldGGdQDydPf7WdGWf0/img.png&quot; data-alt=&quot;먼저 appName에 handling string data로 만들어줬습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HV2WE/dJMcac3vJ56/9C1gldGGdQDydPf7WdGWf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHV2WE%2FdJMcac3vJ56%2F9C1gldGGdQDydPf7WdGWf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;896&quot; height=&quot;411&quot; data-filename=&quot;handling.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;먼저 appName에 handling string data로 만들어줬습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;handling2.png&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cz0CZt/dJMcahRiKuK/0F3fBJp3PhjJL1qLaHxnrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cz0CZt/dJMcahRiKuK/0F3fBJp3PhjJL1qLaHxnrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cz0CZt/dJMcahRiKuK/0F3fBJp3PhjJL1qLaHxnrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcz0CZt%2FdJMcahRiKuK%2F0F3fBJp3PhjJL1qLaHxnrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;877&quot; height=&quot;578&quot; data-filename=&quot;handling2.png&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 us-500이라는 csv를 load해서 가져온걸 확인할수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data에서 address,city,county,state,zip과 first name을 가지고 실습을 해볼예정이라 select해서 가져와서 정리하였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;withColumns를 사용해서 expr와 concat을 사용해 rowdata 를 만들었습니다. expr에서는 address와 city 그리고 county와 state 그리고 zip을 사용해서 한덩어리씩 묶어두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에는 first_name과 address를 10개를 추출한걸 볼수 있습니다.truncate를 줘서 전체 address가 전부 나온걸 확인할수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;하지만 위에 데이터 출력한걸 보면 #이라던지 특수문자들이 많이 나와있는걸 확인할수 있습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 정규식을 통해서 특수문자를 한번 없애보도록 하겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;regexp_replace&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;regexp_replace01.png&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doYyci/dJMcaadA8yc/exfvkjdCGp8M3eUH9PgC10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doYyci/dJMcaadA8yc/exfvkjdCGp8M3eUH9PgC10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doYyci/dJMcaadA8yc/exfvkjdCGp8M3eUH9PgC10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoYyci%2FdJMcaadA8yc%2FexfvkjdCGp8M3eUH9PgC10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;883&quot; height=&quot;477&quot; data-filename=&quot;regexp_replace01.png&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;477&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clean_address라고 하는 새로운 column에 regular_expression을 쓰는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 첫번째 &quot;&quot; = 찾을값 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 두번째 &quot;&quot; = 교체할 값&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중괄호를 이용한 정규식 표현입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에 hat^을 씌운다음 hat = not이라는 뜻입니다. 앞에 공백을 없애줍니다. 대문자,소문자 알파벳을 찾고 \s를 통해서 나머지 띄어쓰기 공백을 살려줬습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;rlike&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rlike.png&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2RPJq/dJMcaiQaXcz/vA4NefCo32FbP20IG97Bl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2RPJq/dJMcaiQaXcz/vA4NefCo32FbP20IG97Bl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2RPJq/dJMcaiQaXcz/vA4NefCo32FbP20IG97Bl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2RPJq%2FdJMcaiQaXcz%2FvA4NefCo32FbP20IG97Bl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;589&quot; height=&quot;315&quot; data-filename=&quot;rlike.png&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sql like문이랑 비슷한 개념이다 filter 조건에 col을 address 안에서 rlike를 사용해 Santa Clara가 있는 address주소를 찾아서 출력하는 모습이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;regexp_extract&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;regexp_extract01.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k5157/dJMcafspNeD/0Ia8ke1sUSiOfHctK9bkS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k5157/dJMcafspNeD/0Ia8ke1sUSiOfHctK9bkS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k5157/dJMcafspNeD/0Ia8ke1sUSiOfHctK9bkS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk5157%2FdJMcafspNeD%2F0Ia8ke1sUSiOfHctK9bkS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1052&quot; height=&quot;498&quot; data-filename=&quot;regexp_extract01.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;regexp_extract02.png&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b30UcW/dJMcah4Q9JS/9qphi5nB15aVsdP80PCBMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b30UcW/dJMcah4Q9JS/9qphi5nB15aVsdP80PCBMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b30UcW/dJMcah4Q9JS/9qphi5nB15aVsdP80PCBMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb30UcW%2FdJMcah4Q9JS%2F9qphi5nB15aVsdP80PCBMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;450&quot; data-filename=&quot;regexp_extract02.png&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extractor는 추출하다는 뜻이다. 결국 앞에서 했던 raw데이터에 컬럼에 해당되는 값을 특정한 규칙들을 정해서 정규 표현식으로 특정한 값을 컬럼으로 추출해내는 과정입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째는 df_clean으로 만들어주고 해당 컬럼에 대해서 지정해준다음 정규식을통해서 추출해내는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;괄호()를 가지고 그룹을 지을수 있습니다. 그리고 뒤에 정수를 사용할수있는데, 숫자 즉 digit로 존재하는 그룹을 찾아서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와같은 방식의 regular Expression에서는 \역슬래시로 digit를 찾을수도있습니다.&lt;/p&gt;</description>
      <category>Spark</category>
      <category>regexp</category>
      <category>RegularExpressionExtract</category>
      <category>RegularExpressionReplace</category>
      <category>rlike</category>
      <category>spark</category>
      <category>데이터엔지니어</category>
      <category>스파크</category>
      <category>정규식</category>
      <author>jyu_seo_</author>
      <guid isPermaLink="true">https://jyu-seo.tistory.com/177</guid>
      <comments>https://jyu-seo.tistory.com/177#entry177comment</comments>
      <pubDate>Mon, 23 Mar 2026 19:45:33 +0900</pubDate>
    </item>
    <item>
      <title>[Spark] - Spark 기본 동작</title>
      <link>https://jyu-seo.tistory.com/176</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. RDD&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;RDD는 &lt;b&gt;&quot;Resilient Distributed Dataset&quot;&lt;/b&gt;의 약자로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;탄력적인 분산 데이터셋&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이라는 의미다. 데이터를 클러스터 내에 분산 저장하고, 하나의 파일처럼 읽고 쓰는 것이 가능하다&lt;b&gt;. 물리적으로 분산저장 되는 것이 아닌, 내부에 Logical Partition이 있고 해당 파티션 기준으로 물리적 노드에서 나누어 처리가 가능하다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;RDD는 불변하는 특징을 가지고있다. RDD에 작업을 하게 되면 하나 혹은 다수의 RDD를 결과 값으로 받게 된다. 즉, 작업한 RDD는 변함없이 존재하고 작업이 반영된 새로운 RDD만 생성되는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. Transformation / Action&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;RDD로 수행하는 동작은 &lt;b&gt;Transformation / Action&lt;/b&gt; 으로 나누어 볼 수 있다. 먼저 Transformation을 살펴보겠다. Transformation은 RDD에 변화를 주는 Spark Operation을 통칭한다. map,filter 등의 Operation이 여기에 속한다. Transformation을 진행하게 되면 새로운 RDD가 생성되고 이러한 변화를 DAG 형태로 나타낼 수 있는데 이것을&lt;b&gt; RDD Lineage 라고 한다.&lt;/b&gt; RDD Lineage를 살펴보면 RDD가 어떻게 변화하는지 그 과정을 한 눈에 볼 수 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRnpHJ/dJMcajuNo8z/H1ikjZOmw5Xg97gGbSPeAK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRnpHJ/dJMcajuNo8z/H1ikjZOmw5Xg97gGbSPeAK/img.jpg&quot; data-alt=&quot;RDD lineage&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRnpHJ/dJMcajuNo8z/H1ikjZOmw5Xg97gGbSPeAK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRnpHJ%2FdJMcajuNo8z%2FH1ikjZOmw5Xg97gGbSPeAK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;641&quot; height=&quot;401&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RDD lineage&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Action은 기존의 RDD로 결과 값을 계산하거나 HDFS와 같은 외부 스토리지에 저장하는 오퍼레이션이다. count,collect 등이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. Lazy Evaluation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Lazy Evaluation이란 &lt;/span&gt;프로그래밍에서 표현식의 계산을 실제 값이 필요한 시점까지 미루는 최적화 기법&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;RDD의 Transformation을 실행해보면 굉장히 빠르게 적용되는 것을 볼 수 있다. 하지만 해당 연산은&lt;b&gt; Action을 사용할때 일어난다.&lt;/b&gt; 이것을&lt;b&gt; Lazy Evaluation이라고 한다.&lt;/b&gt; 즉, Spark는 &lt;b&gt;Action을 만나기 전까지는 Transformation을 수행하지 않는다.&lt;/b&gt; 이렇게 하는 데는 여러 장점이 있기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;1.불필요한 처리 생략&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;ex:) 만약 Transformation을 호출 즉시 바로 실행하게 된다면 비효율적인 상황이 발생할수 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774246479895&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;map 함수를 통해 10만건의 데이터에 1000을 더한 후 추가로 3을 더한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이런 상황이 발생한다면 작업을 2번 처리하게 될 것이다. 하지만 Lazy Evaluation을 통해 Catalyst Optimizer가 물리적인 실행계획을 수립해주기 때문에 효율적으로 데이터 처리가 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;2.메모리 효율성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt; &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;만약 Transformation을 즉시 처리한다면 중간 데이터를 저장할 필요가 있다. 하지만 Lazy Evalution을 통해 필요없는 데이터를 저장하고 삭제하는 비용을 줄일 수 있게 된다.&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4. Narrow / Wide Transformation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Transformation은 2종류가 있다. 먼저 Narrow의 경우 파티션 내에서만 연산이 수행된다. 다른 파티션의 데이터와 무관하기 때문에 shuffle 과정이 없어 빠른 작업이 가능하다 map &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Wide는 여러 파티션에 걸쳐 연산이 수행된다. 파티션 사이의 data 이동인 shuffle가 필수적으로 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WESFX/dJMcabQ7Q3z/m4KCQLFZvqxf7in8txEUk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WESFX/dJMcabQ7Q3z/m4KCQLFZvqxf7in8txEUk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WESFX/dJMcabQ7Q3z/m4KCQLFZvqxf7in8txEUk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWESFX%2FdJMcabQ7Q3z%2Fm4KCQLFZvqxf7in8txEUk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;414&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Narrow Transformation 장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;4835&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;각 파티션이 독립적으로 처리되므로 효율적인 병렬 처리가 가능합니다.&lt;/li&gt;
&lt;li id=&quot;99e7&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;데이터 이동을 최소화하고 네트워크 오버헤드를 줄였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Wide Transformation 장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;1f09&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;데이터 집계, 그룹화 또는 재분배가 필요한 작업에는 광범위한 변환이 필수적입니다.&lt;/li&gt;
&lt;li id=&quot;82b2&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;이러한 기능들을 통해 조인 및 집계와 같은 복잡한 데이터 처리 작업을 수행할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;주요 차이점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;a50a&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;데이터 셔플링:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;협의 변환과 광범위 변환의 가장 중요한 차이점은 데이터 셔플링이 필요한지 여부입니다. 협의 변환은 데이터 셔플링이 필요하지 않지만, 광범위 변환은 필요합니다.&lt;/li&gt;
&lt;li id=&quot;1b3b&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;병렬 처리:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;좁은 범위의 변환은 각 파티션 내에서 효율적인 병렬 처리를 가능하게 하여 병렬성을 향상시킵니다. 반면, 넓은 범위의 변환은 데이터 재배열로 인해 병렬 처리에 장벽을 만들 수 있습니다.&lt;/li&gt;
&lt;li id=&quot;6ecf&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;성능 영향:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;데이터 재배열이 수반되는 대규모 변환은 성능에 더 큰 영향을 미칠 수 있으며 신중한 최적화 및 튜닝이 필요할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;5. Cache &amp;amp; Persist / Checkpoint&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Spark는 반복적인 job을 위해 성능 최적화 방법으로&lt;b&gt; cahce를 통한 반복연산을 사용한다.&lt;/b&gt; chche,persist 그리고 checkpoint가 그 기능을 담당한다. &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;cache와 persist는 RDD는 데이터를 캐시한다.&lt;/b&gt; 두개는 비슷하며 cache를 하면 내부적으로 persist가 수행된다. checkpoint도 비슷하지만 다른 부분이 있다. checkpoint는 데이터를 캐시하여 disk에 저장한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;checkpoint는 RDD와 Streaming에서 서로 다른 목적을 위해 사용된다. RDD는 여러 배치 작업의 Statful한 캐시 데이터 활용을 위해 사용된다. Streaming의 경우 장애대응을 위해 사용하게 된다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spark</category>
      <category>Cache</category>
      <category>Persist</category>
      <category>RDD</category>
      <category>spark</category>
      <category>데이터엔지니어</category>
      <category>스파크</category>
      <author>jyu_seo_</author>
      <guid isPermaLink="true">https://jyu-seo.tistory.com/176</guid>
      <comments>https://jyu-seo.tistory.com/176#entry176comment</comments>
      <pubDate>Mon, 23 Mar 2026 15:21:19 +0900</pubDate>
    </item>
  </channel>
</rss>