<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>devvon</title>
    <link>https://pickwon.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 11 May 2026 10:34:39 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>wonpick</managingEditor>
    <image>
      <title>devvon</title>
      <url>https://tistory1.daumcdn.net/tistory/4724687/attach/8c5948f7904b4896b04eee298cb40c82</url>
      <link>https://pickwon.tistory.com</link>
    </image>
    <item>
      <title>Slack API 호출 (stepfunction , lambda)</title>
      <link>https://pickwon.tistory.com/202</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;0. 개요&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 stepfunction flow 흐름 내부 작업들의 성공/실패 여부를 람다/http로 slack 알림으로 보내고 있었는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 서비스에 적용하다보니 main 메세지로 가게되면 내용파악이 잘안되고 메시지가 너무 많아져서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드로 상세 내용을 담기 위해 slack api를 활용하기로 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;전&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;1246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beS1NZ/btsP1WtqFKC/zLJ5sCef13hbk5urem8i4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beS1NZ/btsP1WtqFKC/zLJ5sCef13hbk5urem8i4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beS1NZ/btsP1WtqFKC/zLJ5sCef13hbk5urem8i4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeS1NZ%2FbtsP1WtqFKC%2FzLJ5sCef13hbk5urem8i4k%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;317&quot; height=&quot;340&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;1246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;후&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/umBIS/btsP1tEOcai/ZThzI6Cans0Ng17IrMGt3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/umBIS/btsP1tEOcai/ZThzI6Cans0Ng17IrMGt3K/img.png&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;654&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.581%; margin-right: 10px;&quot; data-widthpercent=&quot;45.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/umBIS/btsP1tEOcai/ZThzI6Cans0Ng17IrMGt3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FumBIS%2FbtsP1tEOcai%2FZThzI6Cans0Ng17IrMGt3K%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;654&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CccRB/btsP3wUOSoz/ZYNkYBBez0LMIHIaCkEVpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CccRB/btsP3wUOSoz/ZYNkYBBez0LMIHIaCkEVpK/img.png&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;494&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;54.89&quot; data-filename=&quot;blob&quot; style=&quot;width: 54.2562%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CccRB/btsP3wUOSoz/ZYNkYBBez0LMIHIaCkEVpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCccRB%2FbtsP3wUOSoz%2FZYNkYBBez0LMIHIaCkEVpK%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;820&quot; height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/div&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;1. 방법&amp;nbsp;&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;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;1.&amp;nbsp;Slack에서&amp;nbsp;준비할&amp;nbsp;것&amp;nbsp;(Bot&amp;nbsp;Token과&amp;nbsp;Channel&amp;nbsp;ID&amp;nbsp;얻기)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;1단계:&amp;nbsp;Slack&amp;nbsp;앱(App)&amp;nbsp;생성&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;Slack&amp;nbsp;API&amp;nbsp;(&lt;a href=&quot;https://api.slack.com/apps)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.slack.com/apps)&lt;/a&gt; 페이지로 이동하여 Create New App 버튼을 클릭합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;2. From scratch 를 선택하고, 앱 이름(예: &quot;My Workflow Bot&quot;)을 정한 뒤, 알림을 보낼 Slack&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;워크스페이스를 선택.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;2단계:&amp;nbsp;앱에&amp;nbsp;권한(Scopes)&amp;nbsp;부여하기&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1. 생성된 앱의 설정 페이지에서 &lt;b&gt;Features &amp;gt; OAuth &amp;amp; Permissions &lt;/b&gt;&amp;nbsp;메뉴로 이동합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;2. Scopes 섹션까지 스크롤을 내린 후, &lt;b&gt;Bot Token Scopes&lt;/b&gt; 그룹에서 &lt;b&gt;Add an OAuth Scope&lt;/b&gt; 버튼을&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;클릭합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;3.&amp;nbsp;chat:write&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;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KrEyd/btsP00CTxCD/jtok3eQdLZxaRLDhmgGhi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KrEyd/btsP00CTxCD/jtok3eQdLZxaRLDhmgGhi1/img.png&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;1168&quot; data-is-animation=&quot;false&quot; width=&quot;417&quot; height=&quot;284&quot; style=&quot;width: 50.5147%; margin-right: 10px;&quot; data-widthpercent=&quot;51.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KrEyd/btsP00CTxCD/jtok3eQdLZxaRLDhmgGhi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKrEyd%2FbtsP00CTxCD%2Fjtok3eQdLZxaRLDhmgGhi1%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;1714&quot; height=&quot;1168&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIN0r0/btsP19y5f4M/3D8XCbnTkfEfK3Kai2zwP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIN0r0/btsP19y5f4M/3D8XCbnTkfEfK3Kai2zwP0/img.png&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;1164&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.3225%;&quot; data-widthpercent=&quot;48.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIN0r0/btsP19y5f4M/3D8XCbnTkfEfK3Kai2zwP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIN0r0%2FbtsP19y5f4M%2F3D8XCbnTkfEfK3Kai2zwP0%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;1634&quot; height=&quot;1164&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;3단계:&amp;nbsp;앱&amp;nbsp;설치&amp;nbsp;및&amp;nbsp;Bot&amp;nbsp;Token&amp;nbsp;얻기&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1. OAuth &amp;amp; Permissions 페이지 상단으로 다시 올라가 Install to Workspace 버튼을 클릭하여 앱을&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;워크스페이스에&amp;nbsp;설치하고&amp;nbsp;허용합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;2. 설치가 완료되면 Bot User OAuth Token 이 표시됩니다. 이 토큰은 &lt;b&gt;xoxb-&lt;/b&gt;로 시작하며, 이것이 바로&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Lambda 함수에 필요한 &lt;b&gt;SLACK_TOKEN&lt;/b&gt; 입니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;4단계:&amp;nbsp;Channel&amp;nbsp;ID&amp;nbsp;얻기&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;메시지를&amp;nbsp;보낼&amp;nbsp;Slack&amp;nbsp;채널에서&amp;nbsp;채널&amp;nbsp;이름을&amp;nbsp;마우스&amp;nbsp;오른쪽&amp;nbsp;버튼으로&amp;nbsp;클릭합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;&quot;링크&amp;nbsp;복사&quot;를&amp;nbsp;선택합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;3.&amp;nbsp;복사된&amp;nbsp;링크는&amp;nbsp;https://.../archives/C12345678와&amp;nbsp;같은&amp;nbsp;형태이며,&amp;nbsp;여기서&amp;nbsp;마지막&amp;nbsp;부분인&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;`C12345678`&amp;nbsp;이&amp;nbsp;바로&amp;nbsp;`SLACK_CHANNEL_ID`&amp;nbsp;입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-22 오전 10.15.05.png&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;1182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xy3u7/btsP3fZP9m2/HFeEu6a1015oAtFEZBRKZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xy3u7/btsP3fZP9m2/HFeEu6a1015oAtFEZBRKZ1/img.png&quot; data-alt=&quot;다른 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xy3u7/btsP3fZP9m2/HFeEu6a1015oAtFEZBRKZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxy3u7%2FbtsP3fZP9m2%2FHFeEu6a1015oAtFEZBRKZ1%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;566&quot; data-filename=&quot;스크린샷 2025-08-22 오전 10.15.05.png&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;1182&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;br /&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;5단계:&amp;nbsp;채널에&amp;nbsp;봇&amp;nbsp;초대하기&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;메시지를&amp;nbsp;보낼&amp;nbsp;채널에서&amp;nbsp;/invite&amp;nbsp;@&amp;lt;방금&amp;nbsp;만든&amp;nbsp;봇&amp;nbsp;이름&amp;gt;을&amp;nbsp;입력하여&amp;nbsp;봇을&amp;nbsp;채널에&amp;nbsp;초대해야&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;Lambda&amp;nbsp;함수에&amp;nbsp;설정할&amp;nbsp;것&amp;nbsp;(환경&amp;nbsp;변수&amp;nbsp;등록)&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;1170&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pnB7I/btsP1s61pqS/ZmuDE49Ydij8E7bjjpctzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pnB7I/btsP1s61pqS/ZmuDE49Ydij8E7bjjpctzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pnB7I/btsP1s61pqS/ZmuDE49Ydij8E7bjjpctzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpnB7I%2FbtsP1s61pqS%2FZmuDE49Ydij8E7bjjpctzK%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;519&quot; height=&quot;355&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;위에서 얻은 두 개의 값을 Lambda 함수가 코드 안에서 안전하게 사용할 수 있도록 환경 변수로 &amp;nbsp;등록해야 합니다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1. AWS Lambda 관리 콘솔에서 해당 함수 페이지로 이동합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;2. &lt;b&gt;구성(Configuration) 탭 &amp;gt; 환경 변수(Environment variables)&lt;/b&gt; 메뉴로 갑니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;3. 편집(Edit)을 누르고 아래와 같이 두 개의 환경 변수를 추가합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;* 키: &lt;b&gt;SLACK_TOKEN&lt;/b&gt;, 값: 위에서 얻은 xoxb-로 시작하는 토큰&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;*&amp;nbsp;키:&amp;nbsp;&lt;b&gt;SLACK_CHANNEL_ID&lt;/b&gt;,&amp;nbsp;값:&amp;nbsp;위에서&amp;nbsp;얻은&amp;nbsp;C로&amp;nbsp;시작하는&amp;nbsp;채널&amp;nbsp;ID&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 data-ke-size=&quot;size23&quot;&gt;2. slack notification lambda example&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전체적인 구조만 공유 (messages 내용은 미포함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- thread_ts 를 통해 main_res_data와 동일한 time stamp 를 넣어주어 쓰레드로 thread_payload 가 전달될 수 있게 함.&lt;/p&gt;
&lt;pre id=&quot;code_1755831547213&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def slack_msg(msg, username=''):
    slack_uri = os.environ.get(&quot;SLACK_WEBHOOK&quot;)
    headers = {&quot;content-type&quot;: &quot;application/json&quot;, &quot;Accept-Charset&quot;: &quot;UTF-8&quot;}
    try:
        r = requests.post(slack_uri, json={'text': msg, 'username': username}, headers=headers)
        if r.status_code != 200:
            print(f&quot;Error : {r.status_code} {r.text}&quot;)
    except Exception as e:
        print(f&quot;Error slack_msg : {e}&quot;)

def get_user_to_ping(lambda_name: str, mapping: dict, is_debugging: bool, test_user: str, default_user: str) -&amp;gt; str:
    if is_debugging:
        return test_user
    return mapping.get(lambda_name, default_user)
    
def lambda_handler(event, context): 
    messages = [] #담고싶은 메세지를 sf event에서 받아옴
    users_to_ping = set()
    
    ping_string = &quot; &quot;.join(users_to_ping) + &quot;\n&quot; if users_to_ping else &quot;&quot;
    main_message_text = messages[0] #첫 head 메세지
    thread_message_text = ping_string + &quot;\n&quot;.join(messages[1:]) #쓰레드에 달리는 메세지

    slack_bot_token = os.environ.get(&quot;SLACK_TOKEN&quot;)
    slack_channel_id = os.environ.get(&quot;SLACK_CHANNEL_ID&quot;)

    if slack_bot_token and slack_channel_id:
        try:
            api_url = &quot;https://slack.com/api/chat.postMessage&quot;
            headers = {
                &quot;Authorization&quot;: f&quot;Bearer {slack_bot_token}&quot;,
                &quot;Content-Type&quot;: &quot;application/json; charset=utf-8&quot;
            }

            main_payload = {&quot;channel&quot;: slack_channel_id, &quot;text&quot;: main_message_text}
            main_res = requests.post(api_url, headers=headers, json=main_payload)
            main_res.raise_for_status()
            main_res_data = main_res.json()

            if not main_res_data.get(&quot;ok&quot;):
                raise Exception(f&quot;Error : {main_res_data.get('error')}&quot;)

            if thread_message_text.strip():
                message_ts = main_res_data.get(&quot;ts&quot;)
                if message_ts:
                    thread_payload = {
                        &quot;channel&quot;: slack_channel_id,
                        &quot;text&quot;: thread_message_text,
                        &quot;thread_ts&quot;: message_ts
                    }
                    thread_res = requests.post(api_url, headers=headers, json=thread_payload)
                    thread_res.raise_for_status()
                    if not thread_res.json().get(&quot;ok&quot;):
                        print(f&quot;Failed to post thread message: {thread_res.json().get('error')}&quot;)

            return {&quot;statusCode&quot;: 200, &quot;body&quot;: json.dumps(main_res_data)}

        except Exception as e:
            print(error_detail)
            final_text = f&quot;{main_message_text}\n{thread_message_text}&quot;
            slack_msg(f&quot;Slack API 호출 실패. Webhook으로 전체 메시지 전송.\n\n{final_text}&quot;)
            return {&quot;statusCode&quot;: 500, &quot;body&quot;: {e}}
    else:
        print(&quot;Webhook으로 전송 됨&quot;)
        final_text = f&quot;{main_message_text}\n{thread_message_text}&quot;
        slack_msg(final_text)
        return {&quot;statusCode&quot;: 200, &quot;body&quot;: &quot;Sent via webhook.&quot;}&lt;/code&gt;&lt;/pre&gt;</description>
      <author>wonpick</author>
      <guid isPermaLink="true">https://pickwon.tistory.com/202</guid>
      <comments>https://pickwon.tistory.com/202#entry202comment</comments>
      <pubDate>Fri, 22 Aug 2025 12:04:25 +0900</pubDate>
    </item>
    <item>
      <title>EMR Serverless 외부 라이브러리 참조 방법</title>
      <link>https://pickwon.tistory.com/200</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ecr&amp;nbsp;,&amp;nbsp;가상환경&amp;nbsp;압축&amp;nbsp;후&amp;nbsp;s3업로드&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;1.&amp;nbsp;사전&amp;nbsp;준비&amp;nbsp;사항&amp;nbsp;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS&amp;nbsp;CLI가&amp;nbsp;설치&amp;nbsp;및&amp;nbsp;설정되어&amp;nbsp;있어야&amp;nbsp;합니다.&amp;nbsp;(로컬에서&amp;nbsp;하는&amp;nbsp;경우)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Python(3.9&amp;nbsp;등&amp;nbsp;사용&amp;nbsp;버전)&amp;nbsp;및&amp;nbsp;virtualenv&amp;nbsp;또는&amp;nbsp;venv&amp;nbsp;모듈이&amp;nbsp;사용&amp;nbsp;가능한&amp;nbsp;환경이어야&amp;nbsp;합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;동일한&amp;nbsp;CPU&amp;nbsp;아키텍처에서&amp;nbsp;작업을&amp;nbsp;진행해야&amp;nbsp;합니다.&amp;nbsp;예를&amp;nbsp;들어,&amp;nbsp;AWS&amp;nbsp;EMR&amp;nbsp;Serverless의&amp;nbsp;노드가&amp;nbsp;ARM64&amp;nbsp;아키텍처라면,&amp;nbsp;동일한&amp;nbsp;ARM64&amp;nbsp;머신(예:&amp;nbsp;Graviton&amp;nbsp;시리즈(Amazon&amp;nbsp;Linux&amp;nbsp;2/2023))에서&amp;nbsp;가상환경을&amp;nbsp;생성해야&amp;nbsp;합니다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 아키텍쳐를 이해하는데 도움이 된 글 : &lt;a style=&quot;color: #0070d1; text-align: left;&quot; href=&quot;https://velog.io/@480/%EC%9D%B4%EC%A0%9C%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84-CPU-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%A5%BC-%EA%B5%AC%EB%B6%84%ED%95%B4%EC%95%BC-%ED%95%A9%EB%8B%88%EB%8B%A4&quot;&gt;이제는 개발자도 CPU 아키텍처를 구분해야 합니다. (김형섭님)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1743131328209&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;이제는 개발자도 CPU 아키텍처를 구분해야 합니다.&quot; data-og-description=&quot;Intel 천지였던 PC 분야에 ARM이 광풍을 불기 시작한지 얼마 되지 않았습니다.이에 개발자도 변화를 감지 하고 대응하기 위해 알아야 할 것들을 정리 했습니다.그동안 암묵적으로 대부분의 서버 환&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@480/%EC%9D%B4%EC%A0%9C%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84-CPU-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%A5%BC-%EA%B5%AC%EB%B6%84%ED%95%B4%EC%95%BC-%ED%95%A9%EB%8B%88%EB%8B%A4&quot; data-og-url=&quot;https://velog.io/@480/이제는-개발자도-CPU-아키텍처를-구분해야-합니다&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/coaYUz/hyYyPEvRwk/CYTGHoc8JG2BfkQk5L1Ks1/img.png?width=606&amp;amp;height=448&amp;amp;face=0_0_606_448,https://scrap.kakaocdn.net/dn/tsxEA/hyYvgX6huZ/Z5deUKiGG9E0G6K7LvqzL0/img.png?width=606&amp;amp;height=448&amp;amp;face=0_0_606_448,https://scrap.kakaocdn.net/dn/YDVSG/hyYxRv0AyA/AjpDv94uC3PLoMRrkeKgI1/img.png?width=606&amp;amp;height=448&amp;amp;face=0_0_606_448&quot;&gt;&lt;a href=&quot;https://velog.io/@480/%EC%9D%B4%EC%A0%9C%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84-CPU-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%A5%BC-%EA%B5%AC%EB%B6%84%ED%95%B4%EC%95%BC-%ED%95%A9%EB%8B%88%EB%8B%A4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@480/%EC%9D%B4%EC%A0%9C%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84-CPU-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%A5%BC-%EA%B5%AC%EB%B6%84%ED%95%B4%EC%95%BC-%ED%95%A9%EB%8B%88%EB%8B%A4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/coaYUz/hyYyPEvRwk/CYTGHoc8JG2BfkQk5L1Ks1/img.png?width=606&amp;amp;height=448&amp;amp;face=0_0_606_448,https://scrap.kakaocdn.net/dn/tsxEA/hyYvgX6huZ/Z5deUKiGG9E0G6K7LvqzL0/img.png?width=606&amp;amp;height=448&amp;amp;face=0_0_606_448,https://scrap.kakaocdn.net/dn/YDVSG/hyYxRv0AyA/AjpDv94uC3PLoMRrkeKgI1/img.png?width=606&amp;amp;height=448&amp;amp;face=0_0_606_448');&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;이제는 개발자도 CPU 아키텍처를 구분해야 합니다.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Intel 천지였던 PC 분야에 ARM이 광풍을 불기 시작한지 얼마 되지 않았습니다.이에 개발자도 변화를 감지 하고 대응하기 위해 알아야 할 것들을 정리 했습니다.그동안 암묵적으로 대부분의 서버 환&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 133px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot; data-start=&quot;1835&quot; data-end=&quot;2583&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;x86_64 (AMD/Intel)&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;ARM64 (ARM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;&lt;b&gt;명령어 집합&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;CISC 기반&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;RISC 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;&lt;b&gt;성능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;단일 코어 성능이 높고, 데스크톱/서버에서 전통적으로 강점&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;코어 수 증가, 전력 효율 우수, 다중 코어 활용에 강점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;&lt;b&gt;전력 소모&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;상대적으로 높음&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;상대적으로 낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;&lt;b&gt;소프트웨어 호환&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;매우 폭넓은 호환성&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 19px;&quot;&gt;최근 빠르게 확대 중, 일부 에뮬레이션 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 38px;&quot;&gt;&lt;b&gt;주요 사용처&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 38px;&quot;&gt;전통적 데스크톱, 노트북, 서버(Windows/리눅스/맥 인텔 기반)&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 38px;&quot;&gt;모바일(스마트폰/태블릿), 임베디드,&lt;br /&gt;저전력 서버(AWS Graviton), 리눅스, 애플 M 시리즈 맥&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;2.&amp;nbsp;가상환경&amp;nbsp;생성&amp;nbsp;및&amp;nbsp;라이브러리&amp;nbsp;설치&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은&amp;nbsp;ARM64&amp;nbsp;환경을&amp;nbsp;예로&amp;nbsp;들어&amp;nbsp;설명합니다.&amp;nbsp;x86_64&amp;nbsp;아키텍처라면&amp;nbsp;동일한&amp;nbsp;방식으로&amp;nbsp;진행하되&amp;nbsp;머신&amp;nbsp;아키텍처만&amp;nbsp;맞추시면&amp;nbsp;됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1743136128423&quot; class=&quot;mipsasm&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Python 3.9 기준 예시
python3.9 -m venv brotli_env
source brotli_env/bin/activate

# 필요한 라이브러리 설치 (예: brotli)
pip install brotli&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark&amp;nbsp;작업에서&amp;nbsp;사용하고&amp;nbsp;싶은&amp;nbsp;Python&amp;nbsp;라이브러리가&amp;nbsp;있다면&amp;nbsp;모두&amp;nbsp;여기에서&amp;nbsp;설치해두면&amp;nbsp;됩니다.&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;3.&amp;nbsp;가상환경&amp;nbsp;폴더&amp;nbsp;내부&amp;nbsp;압축하기&amp;nbsp;및&amp;nbsp;S3&amp;nbsp;업로드&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;lt;경로&amp;nbsp;중복&amp;nbsp;방지!!!&amp;gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상환경&amp;nbsp;폴더&amp;nbsp;자체를&amp;nbsp;통째로&amp;nbsp;tar.gz로&amp;nbsp;압축하면,&amp;nbsp;Spark에서&amp;nbsp;--archives&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;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-start=&quot;872&quot; data-end=&quot;1058&quot;&gt;예를 들어, brotli_env라는 폴더를 통째로 압축하면, 압축 해제 시 brotli_env/brotli_env/처럼 경로가 중복될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를&amp;nbsp;방지하기&amp;nbsp;위해,&amp;nbsp;가상환경&amp;nbsp;폴더&amp;nbsp;내부의&amp;nbsp;파일들과&amp;nbsp;서브&amp;nbsp;폴더만&amp;nbsp;압축해야&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1743136166436&quot; class=&quot;elixir&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# brotli_env 폴더 내부로 이동
(brotli_env) [ec2-user@dawon test]$ cd brotli_env/
# 폴더 내부의 모든 내용(.* 포함)을 tar.gz로 압축
# (.*까지 포함하려면 숨김 파일/폴더까지 확인 필요)
(brotli_env) [ec2-user@dawon brotli_env]$ tar -czf ../brotli_env_arm64.tar.gz *
# 현재 디렉토리를 벗어나기
(brotli_env) [ec2-user@dawon brotli_env]$ cd ..
#brotli_env 폴더 구조를 유지하면서, 압축 해제 시 디렉토리 중복안됨
(brotli_env) [ec2-user@dawon test]$ ls
brotli_env  brotli_env_arm64.tar.gz

#Amazon S3 버킷에 업로드
(brotli_env) [ec2-user@dawon test]$ aws s3 cp brotli_env_arm64.tar.gz s3://bucket/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;과정을&amp;nbsp;거치면&amp;nbsp;brotli_env_arm64.tar.gz&amp;nbsp;압축&amp;nbsp;파일이&amp;nbsp;생성됩니다.&amp;nbsp;이&amp;nbsp;파일은&amp;nbsp;기존&amp;nbsp;brotli_env&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 해야지 경로가 아래와 같이 할당 됨 ['./brotli_env/bin', './brotli_env/lib64'] &lt;br /&gt;-&amp;gt; 이전에는 ['./brotli_env/brotli_env/bin', './brotli_env/brotli_env/lib64']&lt;br /&gt;&lt;br /&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;image-20250214-051019.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XshXV/btsMZPjxOIz/ntMiYEB6xGuwROuKQMwmYk/tfile.dat&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XshXV/btsMZPjxOIz/ntMiYEB6xGuwROuKQMwmYk/tfile.dat&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XshXV/btsMZPjxOIz/ntMiYEB6xGuwROuKQMwmYk/tfile.dat&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXshXV%2FbtsMZPjxOIz%2FntMiYEB6xGuwROuKQMwmYk%2Ftfile.dat&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;1512&quot; height=&quot;226&quot; data-filename=&quot;image-20250214-051019.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;226&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;4.&amp;nbsp;EMR&amp;nbsp;Serverless&amp;nbsp;Spark&amp;nbsp;작업&amp;nbsp;제출&amp;nbsp;&lt;/h4&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&amp;nbsp;Submit과&amp;nbsp;유사한&amp;nbsp;방식으로,&amp;nbsp;EMR&amp;nbsp;Serverless에서는&amp;nbsp;--archives&amp;nbsp;옵션을&amp;nbsp;통해&amp;nbsp;Python&amp;nbsp;가상환경&amp;nbsp;압축&amp;nbsp;파일을&amp;nbsp;넘길&amp;nbsp;수&amp;nbsp;있습니다.&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;--archives&amp;nbsp;s3://&amp;lt;버킷명&amp;gt;/brotli_env_arm64.tar.gz#brotli_env&amp;nbsp;&lt;br /&gt;-&amp;gt;&amp;nbsp;이&amp;nbsp;옵션은&amp;nbsp;압축&amp;nbsp;파일을&amp;nbsp;brotli_env&amp;nbsp;디렉터리로&amp;nbsp;풀겠다는&amp;nbsp;의미입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Spark&amp;nbsp;실행&amp;nbsp;시&amp;nbsp;spark.emr-serverless.driverEnv.PYTHONPATH와&amp;nbsp;spark.executorEnv.PYTHONPATH를&amp;nbsp;설정해&amp;nbsp;Python&amp;nbsp;라이브러리를&amp;nbsp;찾을&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;spark.emr-serverless.driverEnv.PYSPARK_PYTHON,&amp;nbsp;spark.executorEnv.PYSPARK_PYTHON&amp;nbsp;환경&amp;nbsp;변수를&amp;nbsp;가상환경의&amp;nbsp;Python&amp;nbsp;바이너리&amp;nbsp;경로로&amp;nbsp;지정해야&amp;nbsp;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743136272498&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#cli 예시
aws emr-serverless start-job-run\
     --application-id {app id}     \
     --execution-role-arn {arn}     \
     --job-driver '{                                                                                                                                                                                
        &quot;sparkSubmit&quot;: {
            &quot;entryPoint&quot;: &quot;s3://bucket/import_brotli.py&quot;,
            &quot;sparkSubmitParameters&quot;: \
            &quot;--archives s3://bucket/brotli_env_arm64.tar.gz#brotli_env\
              --conf spark.emr-serverless.driverEnv.PYTHONPATH=./brotli_env/lib/python3.9/site-packages\
               --conf spark.executorEnv.PYTHONPATH=./brotli_env/lib/python3.9/site-packages\
                --conf spark.emr-serverless.driverEnv.PYSPARK_PYTHON=./brotli_env/bin/python3.9\
                 --conf spark.executorEnv.PYSPARK_PYTHON=./brotli_env/bin/python3.9&quot;
        }
    }'&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발/Spark</category>
      <author>wonpick</author>
      <guid isPermaLink="true">https://pickwon.tistory.com/200</guid>
      <comments>https://pickwon.tistory.com/200#entry200comment</comments>
      <pubDate>Fri, 28 Mar 2025 12:08:56 +0900</pubDate>
    </item>
    <item>
      <title>sublime text 단축키 정리</title>
      <link>https://pickwon.tistory.com/197</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;아래 표는 Sublime Text 4 (Build 4192 기준)으로 Windows/Linux와 macOS에서 주로 사용되는 단축키들을 정리한 것입니다.&lt;br /&gt;(기본 설정 그대로일 때의 단축키이며, 사용자가 Key Bindings를 변경했거나 플러그인에 따라 다를 수 있습니다.)&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;1. 기본 파일/창 관리&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 247px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;기능&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Windows/Linux&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;macOS&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;새 파일(탭)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Ctrl + N&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Cmd + N&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;새로운 빈 탭(파일) 열기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;새 창&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Ctrl + Shift + N&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Cmd + Shift + N&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Sublime Text 새 창 열기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;파일 열기&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Ctrl + O&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Cmd + O&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;파일 열기 대화상자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;파일 저장&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Ctrl + S&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Cmd + S&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;현재 파일 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;다른 이름으로 저장&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Ctrl + Shift + S&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Cmd + Shift + S&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;현재 파일을 다른 이름으로 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;닫기(현재 탭)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Ctrl + W&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Cmd + W&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;현재 탭 닫기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;모든 탭 닫기&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Ctrl + K, Ctrl + W&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Cmd + K, Cmd + W&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;열려 있는 모든 탭 닫기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;이전에 닫았던 탭 다시 열기&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Ctrl + Shift + T&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Cmd + Shift + T&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;방금 닫은 파일(탭)을 다시 열기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;사이드바 토글&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Ctrl + K, Ctrl + B&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Cmd + K, Cmd + B&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;좌측 사이드바 표시/숨기기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;전체화면 모드 토글&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;F11 / Alt + F11 (Win)&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Cmd + Ctrl + F (macOS)&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;기본 전체화면 혹은 방해금지 모드(Distraction Free) 토글&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;h3 data-ke-size=&quot;size23&quot;&gt;2. 편집 관련 (줄/단어/구문)&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 573px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;기능&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Windows/Linux&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;macOS&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;주석 토글 (현재 줄/선택 영역)&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Ctrl + /&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Cmd + /&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;선택된 줄(또는 영역)에 한 줄 주석 추가/해제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;라인 복제&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;Ctrl + Shift + D&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;Cmd + Shift + D&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;현재 줄(또는 선택 영역)을 바로 아래에 복제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;라인 삭제&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;Ctrl + Shift + K&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;Cmd + Shift + K&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;커서가 있는 줄(또는 선택 영역 전체) 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;라인 이동(위/아래)&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;Ctrl + Shift + &amp;uarr;/&amp;darr;&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;Cmd + Ctrl + &amp;uarr;/&amp;darr;&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;현재 줄(또는 선택 영역)을 위/아래로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;한 줄 선택&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Ctrl + L (반복)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Cmd + L (반복)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;현재 줄 전체를 선택, 반복 시 인접한 줄로 확장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;여러 줄(행) 분할(행 분리)&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Ctrl + Shift + L&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Cmd + Shift + L&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;여러 줄을 한꺼번에 선택했을 때 &lt;b&gt;각 줄별&lt;/b&gt;로 커서(멀티커서) 분할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;단어 단위 선택(반복)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;Ctrl + D (여러 번)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;Cmd + D (여러 번)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;커서가 위치한 단어 선택, 반복 입력 시 다음 동일 단어 추가 선택&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;최근 추가된 단어 선택 해제&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;Ctrl + K, Ctrl + D&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;Cmd + K, Cmd + D&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;직전에 추가된 멀티 선택(단어)을 하나씩 제거&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;문서 내 동일 단어 &lt;b&gt;전체&lt;/b&gt; 선택&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Alt + F3&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Ctrl + Cmd + G&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;문서 내 동일 단어를 한 번에 모두 선택 (멀티 커서 생성)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;멀티 선택 해제(Soft Undo)&lt;/td&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;Ctrl + U&lt;/td&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;Cmd + U&lt;/td&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;멀티로 선택된 커서(단어) 중 마지막 추가분을 한 번에 해제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;모든 선택 취소&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Esc&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Esc&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;멀티 커서/선택 전부 해제, 검색 강조 취소 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;괄호 범위 선택&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;Ctrl + Shift + M&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;Cmd + Shift + M&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;괄호((), {}, []) 안쪽의 내용을 &lt;b&gt;포함&lt;/b&gt;하여 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;들여쓰기 레벨 동일 범위 선택&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Ctrl + Shift + J&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Cmd + Shift + J&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;현재 코드 블록과 같은 &lt;b&gt;들여쓰기 레벨&lt;/b&gt;에 있는 구역 전체 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;같은 스코프(Scope) 범위 선택&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Ctrl + Shift + Space&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Cmd + Shift + Space&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;함수, 객체 등 Sublime이 인식하는 같은 &lt;b&gt;스코프&lt;/b&gt; 내 영역을 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;전체 코드 자동 정렬 (Reindent)&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;b&gt;Command Palette&lt;/b&gt;에서 Reindent&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;같음&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;선택 영역(또는 파일 전체)을 &lt;b&gt;자동 들여쓰기&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;매크로 녹화/중단/재생&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;Ctrl + Q / 동일 / Ctrl + Shift + Q&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;macOS 미리 설정 없음&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;편집 작업을 매크로로 녹화 후 재실행 (Windows/Linux 기본). macOS는 사용자가 직접 바인딩 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 검색 &amp;amp; 치환&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기능&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Windows/Linux&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;macOS&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;찾기(Find)&lt;/td&gt;
&lt;td&gt;Ctrl + F&lt;/td&gt;
&lt;td&gt;Cmd + F&lt;/td&gt;
&lt;td&gt;현재 문서 내 문자열 검색 (Find Bar 표시)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;바꾸기(Replace)&lt;/td&gt;
&lt;td&gt;Ctrl + H&lt;/td&gt;
&lt;td&gt;Cmd + Alt + F&lt;/td&gt;
&lt;td&gt;현재 문서 내 문자열 치환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파일 전체 검색(Find in Files)&lt;/td&gt;
&lt;td&gt;Ctrl + Shift + F&lt;/td&gt;
&lt;td&gt;Cmd + Shift + F&lt;/td&gt;
&lt;td&gt;지정한 폴더/프로젝트 전체에서 검색/치환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검색 결과 다음 이동&lt;/td&gt;
&lt;td&gt;F3&lt;/td&gt;
&lt;td&gt;Cmd + G&lt;/td&gt;
&lt;td&gt;검색 결과(Find) 다음 위치로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검색 결과 이전 이동&lt;/td&gt;
&lt;td&gt;Shift + F3&lt;/td&gt;
&lt;td&gt;Shift + Cmd + G&lt;/td&gt;
&lt;td&gt;검색 결과 이전 위치로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빠른 검색(Incremental Find)&lt;/td&gt;
&lt;td&gt;Ctrl + I&lt;/td&gt;
&lt;td&gt;Cmd + I&lt;/td&gt;
&lt;td&gt;입력할 때마다 즉시 검색 (상단 표시줄로 수행)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 탐색 &amp;amp; Goto&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기능&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Windows/Linux&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;macOS&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goto Anything (파일/기호/줄 등)&lt;/td&gt;
&lt;td&gt;Ctrl + P&lt;/td&gt;
&lt;td&gt;Cmd + P&lt;/td&gt;
&lt;td&gt;파일 이름, @ 기호(함수/클래스), : 줄번호 등 빠른 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;특정 줄 번호로 이동&lt;/td&gt;
&lt;td&gt;Ctrl + G&lt;/td&gt;
&lt;td&gt;Cmd + G&lt;/td&gt;
&lt;td&gt;예: Ctrl + G &amp;rarr; 숫자 입력 &amp;rarr; 해당 줄로 점프&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;현재 파일 내 심볼(함수/클래스)&lt;/td&gt;
&lt;td&gt;Ctrl + R&lt;/td&gt;
&lt;td&gt;Cmd + R&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;/td&gt;
&lt;td&gt;Ctrl + Shift + R&lt;/td&gt;
&lt;td&gt;Cmd + Shift + R&lt;/td&gt;
&lt;td&gt;열려 있는 프로젝트(또는 폴더) 내 모든 파일의 심볼 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정의(Definition)로 이동&lt;/td&gt;
&lt;td&gt;F12&lt;/td&gt;
&lt;td&gt;F12 (같음)&lt;/td&gt;
&lt;td&gt;선택한 심볼의 정의 위치로 점프 (언어/플러그인 지원 시)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;명령 팔레트 열기&lt;/td&gt;
&lt;td&gt;Ctrl + Shift + P&lt;/td&gt;
&lt;td&gt;Cmd + Shift + P&lt;/td&gt;
&lt;td&gt;Sublime Text &lt;b&gt;Command Palette&lt;/b&gt;(명령 검색/실행 창) 열기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 멀티 커서 &amp;amp; 컬럼 선택 (마우스 포함)&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기능&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Windows/Linux&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;macOS&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;임의 위치에 커서 추가&lt;/td&gt;
&lt;td&gt;Alt + 클릭&lt;/td&gt;
&lt;td&gt;Option + 클릭&lt;/td&gt;
&lt;td&gt;원하는 지점마다 클릭하여 커서를 여러 개 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컬럼(열) 선택 (마우스 드래그)&lt;/td&gt;
&lt;td&gt;Shift + 우클릭 드래그 또는 &lt;b&gt;가운데 버튼&lt;/b&gt; 드래그&lt;/td&gt;
&lt;td&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;/td&gt;
&lt;td&gt;Ctrl + D (반복)&lt;/td&gt;
&lt;td&gt;Cmd + D (반복)&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;/td&gt;
&lt;td&gt;Alt + F3&lt;/td&gt;
&lt;td&gt;Ctrl + Cmd + G&lt;/td&gt;
&lt;td&gt;문서 내 동일 단어를 모두 한 번에 멀티 커서로 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;선택 해제 (하나씩 취소)&lt;/td&gt;
&lt;td&gt;Ctrl + U&lt;/td&gt;
&lt;td&gt;Cmd + U&lt;/td&gt;
&lt;td&gt;마지막으로 추가된 커서(선택) 하나씩 되돌림(Soft Undo)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 창(화면) 분할 &amp;amp; 탭 이동&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기능&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Windows/Linux&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;macOS&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;뷰 분할(2분할/3분할 등)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;메뉴&lt;/b&gt;: View &amp;gt; Layout&lt;/td&gt;
&lt;td&gt;&lt;b&gt;메뉴&lt;/b&gt;: View &amp;gt; Layout&lt;/td&gt;
&lt;td&gt;화면을 세로/가로/그리드 등으로 나누어 여러 파일을 동시 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;다음 그룹(패널)로 이동&lt;/td&gt;
&lt;td&gt;Ctrl + K, Ctrl + Right&lt;/td&gt;
&lt;td&gt;Cmd + K, Cmd + Right&lt;/td&gt;
&lt;td&gt;분할된 뷰 사이를 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이전 그룹(패널)로 이동&lt;/td&gt;
&lt;td&gt;Ctrl + K, Ctrl + Left&lt;/td&gt;
&lt;td&gt;Cmd + K, Cmd + Left&lt;/td&gt;
&lt;td&gt;분할된 뷰 사이 이동 (반대 방향)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;다음 탭 이동&lt;/td&gt;
&lt;td&gt;Ctrl + Tab (또는 Ctrl + PageDown)&lt;/td&gt;
&lt;td&gt;Cmd + Alt + Right (또는 Cmd + Shift + ])&lt;/td&gt;
&lt;td&gt;여러 탭이 열려 있을 때 오른쪽 탭으로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이전 탭 이동&lt;/td&gt;
&lt;td&gt;Ctrl + Shift + Tab (또는 Ctrl + PageUp)&lt;/td&gt;
&lt;td&gt;Cmd + Alt + Left (또는 Cmd + Shift + [)&lt;/td&gt;
&lt;td&gt;여러 탭이 열려 있을 때 왼쪽 탭으로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;현재 탭 닫기&lt;/td&gt;
&lt;td&gt;Ctrl + W&lt;/td&gt;
&lt;td&gt;Cmd + W&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;h3 data-ke-size=&quot;size23&quot;&gt;7.&amp;nbsp; 기타 자주 쓰는 단축키&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기능&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Windows/Linux&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;macOS&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자동 완성(팝업) 표시&lt;/td&gt;
&lt;td&gt;Ctrl + Space&lt;/td&gt;
&lt;td&gt;Ctrl + Space&lt;/td&gt;
&lt;td&gt;코드/텍스트 자동완성 (기본 제공)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;레이아웃 전환 (2-Column, 3-Column 등)&lt;/td&gt;
&lt;td&gt;Alt + Shift + 2 (등등)&lt;/td&gt;
&lt;td&gt;Cmd + Option + 2 (등등)&lt;/td&gt;
&lt;td&gt;메뉴의 View &amp;gt; Layout &amp;gt; Columns/Rows 를 단축키로 (기본값은 OS나 버전에 따라 다를 수 있음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;매개변수 정보/도움말(플러그인에 따라 다름)&lt;/td&gt;
&lt;td&gt;(기본 단축키 없음)&lt;/td&gt;
&lt;td&gt;(기본 단축키 없음)&lt;/td&gt;
&lt;td&gt;LSP 등의 플러그인 설치 시 설정 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;콘솔 열기 (Sublime 내부)&lt;/td&gt;
&lt;td&gt;Ctrl + ` (백틱 키)&lt;/td&gt;
&lt;td&gt;Ctrl + `&lt;/td&gt;
&lt;td&gt;Sublime Text 내부 콘솔 창을 열기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프로젝트 열기&lt;/td&gt;
&lt;td&gt;Ctrl + Alt + P&lt;/td&gt;
&lt;td&gt;Cmd + Alt + P&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Quick Switch Project&lt;/b&gt;: 최근 프로젝트 목록에서 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실행(빌드)&lt;/td&gt;
&lt;td&gt;Ctrl + B&lt;/td&gt;
&lt;td&gt;Cmd + B&lt;/td&gt;
&lt;td&gt;Build 시스템이 설정된 경우 현재 파일(프로젝트) 빌드/실행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 참고 사항&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Sublime Text 4 (Build 4192) 기준&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커스텀 Key Bindings&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제로 쓰시면서 필요한 단축키가 충돌하거나 불편하다면, Preferences &amp;gt; Key Bindings에서 직접 수정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플러그인/패키지&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LSP, GitGutter 등 다양한 패키지를 설치하면 추가 단축키가 생기거나 기존 단축키가 재정의될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명령 팔레트( Ctrl + Shift + P / Cmd + Shift + P )&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Sublime Text에서 거의 모든 명령을 &lt;b&gt;검색&lt;/b&gt;해서 실행할 수 있는 창이므로, 단축키가 기억나지 않을 때 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>개발/ETC</category>
      <author>wonpick</author>
      <guid isPermaLink="true">https://pickwon.tistory.com/197</guid>
      <comments>https://pickwon.tistory.com/197#entry197comment</comments>
      <pubDate>Wed, 12 Mar 2025 17:14:42 +0900</pubDate>
    </item>
    <item>
      <title>코드트리 두 달 사용 후기: 실무자가 추천하는 효과적인 코딩테스트 준비법</title>
      <link>https://pickwon.tistory.com/196</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 포스팅은 코드트리 x 글또 블로그 챌린지 2기를 통해 코드트리 체험권을 받아 작성한 후기입니다.&lt;/blockquote&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;936&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FMLJD/btsMFrQyDxR/WjiMDA7EDY7dSk0RCpsGX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FMLJD/btsMFrQyDxR/WjiMDA7EDY7dSk0RCpsGX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FMLJD/btsMFrQyDxR/WjiMDA7EDY7dSk0RCpsGX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFMLJD%2FbtsMFrQyDxR%2FWjiMDA7EDY7dSk0RCpsGX0%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;168&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;422&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;b&gt;코드트리 &amp;times; 글또 블로그 챌린지 2기&lt;/b&gt;에 참여하며 두 달간 코드트리를 이용해봤는데요, 이번에는 실무에서 느낀 점과 실제 코딩테스트에 얼마나 도움이 되었는지 공유해 보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;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;저는 Intermediate Low 단계에서 시작해 BFS, DFS, DP와 같은 알고리즘 문제를 중점적으로 공부했는데요, 덕분에 실무나 코딩테스트에서 자주 접하는 문제 유형을 보다 체계적으로 학습할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 코딩테스트를 대비해 삼성이나 구글 같은 주요 기업의 실제 난이도와 유사한 문제들이 제공되어 실전 감각을 높이는 데 큰 도움이 되었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. GitHub 연동 기능으로 체계적인 관리 가능&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFcppX/btsMF6SFVOj/mFCcv2kBz1bc23Xzp1S4Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFcppX/btsMF6SFVOj/mFCcv2kBz1bc23Xzp1S4Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFcppX/btsMF6SFVOj/mFCcv2kBz1bc23Xzp1S4Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFcppX%2FbtsMF6SFVOj%2FmFCcv2kBz1bc23Xzp1S4Kk%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;377&quot; height=&quot;198&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;514&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;작년보다 더욱 개선된 GitHub 연동 기능 덕분에 풀이한 코드를 자동으로 업로드하고 손쉽게 관리할 수 있었습니다. 풀이 과정을 기록하고, 주기적으로 복습하면서 제 코드가 얼마나 효율적으로 발전하고 있는지를 한눈에 확인할 수 있어 편리했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실무자가 본 코드트리의 실질적 가치&lt;/h3&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;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 아쉬웠던 점들&lt;/h3&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;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;li&gt;UI 개선 이후 메뉴 찾기가 다소 어려워진 점&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 점들은 향후 코드트리가 개선하면 좋을 것 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 두 달간의 변화, 그리고 앞으로의 계획&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 달 동안 코드트리를 꾸준히 이용하며 확실히 코딩테스트에 대한 자신감이 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 전형에서 진행하는 코딩테스트 환경과 비슷한 문제들을 코드트리를 통해 경험해볼 수 있었고,&lt;br /&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;특히 IT 기업에서의 실무 능력 향상까지 고려하고 있다면, 코드트리가 더욱 큰 도움이 될 것입니다.&lt;/p&gt;</description>
      <author>wonpick</author>
      <guid isPermaLink="true">https://pickwon.tistory.com/196</guid>
      <comments>https://pickwon.tistory.com/196#entry196comment</comments>
      <pubDate>Sun, 9 Mar 2025 22:38:47 +0900</pubDate>
    </item>
    <item>
      <title>Spark에서 .option(&amp;quot;someKey&amp;quot;, &amp;quot;someValue&amp;quot;) 와 같이 정의되지 않은 옵션을 줘도 에러가 나지 않는 이유</title>
      <link>https://pickwon.tistory.com/195</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아래 글은 &lt;b&gt;Apache Spark&lt;/b&gt;에서 데이터를 읽거나 쓸 때, .option(&quot;key&quot;, &quot;value&quot;) 형태로 임의 옵션을 넘기면 왜 에러가 나지 않고 무시될 수 있는지, 그리고 &lt;b&gt;MongoDB Spark 커넥터&lt;/b&gt;처럼 &lt;b&gt;버전에 따라 옵션 이름&lt;/b&gt;이 달라질 때 어떤 일이 생기는지 간단히 정리한 포스트입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark에서는 DataFrameReader(또는 DataFrameWriter)를 사용할 때&lt;br /&gt;예)&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;df = (
    spark.read
        .format(&quot;mongodb&quot;)
        .option(&quot;uri&quot;, &quot;mongodb://...&quot;)
        .option(&quot;pipeline&quot;, '[{ &quot;$project&quot;: {...} }]')  # 예: 파이프라인 옵션
        .load()
)
&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;이런 식으로 .option(&quot;key&quot;, &quot;value&quot;)로 설정값을 넣습니다.&lt;br /&gt;하지만 &lt;b&gt;정의되지 않은 옵션&lt;/b&gt;이나 &lt;b&gt;버전마다 다른 키명&lt;/b&gt;을 사용할 경우, Spark가&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;&lt;b&gt; 해당 옵션을 모르겠다&lt;/b&gt;&lt;/span&gt;며 &lt;b&gt;즉시 에러를 내지 않고 그냥 무시&lt;/b&gt;해버리는 상황이 발생할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Spark의 옵션 전달 구조&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) Spark 자체가 옵션 키를 직접 검증하지 않는다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark Core 레벨에서 .option(&quot;someKey&quot;, &quot;someValue&quot;)는 내부적으로 (String -&amp;gt; String) 매핑을 만들어, &lt;br /&gt;&lt;b&gt;해당 DataSource(커넥터)로 그대로 전달&lt;/b&gt;합니다. &lt;br /&gt;Spark는 &amp;ldquo;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;이 옵션이 맞는지 틀린지&lt;/span&gt;&amp;rdquo;를 미리 판단하지 않습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) DataSource(커넥터) 쪽에서만 유효성 검사&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt; &lt;b&gt;MongoDB Spark 커넥터&lt;/b&gt;나 &lt;b&gt;CSV/JSON 리더&lt;/b&gt;&lt;/span&gt; 등이 &amp;ldquo;&lt;b&gt;이 옵션을 지원하느냐 마느냐&lt;/b&gt;&amp;rdquo;를 결정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 커넥터가 pipeline 옵션을 인식하면 쓰고,&lt;/li&gt;
&lt;li&gt;인식하지 못하는(정의되지 않은) 옵션이면 &lt;b&gt;무시&lt;/b&gt;하고 별도 에러 메시지를 주지 않는 구현이 많습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;키 이름이 틀리거나&lt;/b&gt;, &lt;b&gt;커넥터 버전이 달라서 옵션 이름이 바뀌었거나&lt;/b&gt;, &lt;b&gt;그냥 오탈자&lt;/b&gt;를 넣더라도 Spark는 문제삼지 않고 넘어가는 거죠.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgV8V3/btsMEmhBWcH/FQ9vdvXoPz2Ws5CL94dCFK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgV8V3/btsMEmhBWcH/FQ9vdvXoPz2Ws5CL94dCFK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgV8V3/btsMEmhBWcH/FQ9vdvXoPz2Ws5CL94dCFK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgV8V3%2FbtsMEmhBWcH%2FFQ9vdvXoPz2Ws5CL94dCFK%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;264&quot; height=&quot;165&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. MongoDB Spark 커넥터 버전에 따른 예시&lt;/h2&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;구 버전&lt;/b&gt; 커넥터에서는 aggregation.pipeline 라는 키를 써야하고&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최신 버전&lt;/b&gt;에서는 pipeline 키로 받도록 바뀌었다고 가정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 최신 버전에 맞춰 예시 코드를 봤는데, 내 프로젝트의 커넥터 버전이 낮아서 &lt;b&gt;pipeline 키를 인식하지 못한다면&lt;/b&gt;, 아래처럼 작성해도:&lt;/p&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;df = (
    spark.read.format(&quot;mongodb&quot;)
    .option(&quot;uri&quot;, &quot;mongodb://...&quot;)
    .option(&quot;pipeline&quot;, '[{ &quot;$project&quot;: {...} }]')  # &amp;lt;- 커넥터가 모르는 옵션
    .load()
)
&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;될 뿐, 스파크가 &amp;ldquo;&lt;b&gt;pipeline이라는 옵션은 지원되지 않습니다&lt;/b&gt;&amp;rdquo;라는 에러를 내주지 않습니다. 결과적으로 파이프라인이 적용되지 않은 채로 데이터를 읽게 됩니다(또는 스키마 충돌을 일으킬 수도 있고요).&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBj45r/btsMED4x2NP/2LM4NOrkOiDkRY9BumUBv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBj45r/btsMED4x2NP/2LM4NOrkOiDkRY9BumUBv0/img.png&quot; data-alt=&quot;이렇게 filtering하는 pipeline 옵션 (select (Projection)을 무시하고 전체 필드를 다 불러옴&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBj45r/btsMED4x2NP/2LM4NOrkOiDkRY9BumUBv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBj45r%2FbtsMED4x2NP%2F2LM4NOrkOiDkRY9BumUBv0%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;462&quot; height=&quot;510&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;930&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이렇게 filtering하는 pipeline 옵션 (select (Projection)을 무시하고 전체 필드를 다 불러옴&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시는 pipeline옵션이 몽고 커넥터의 버전(mongodb spark connector10.1.1) 에 없는 옵션값이어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제대로 select 기능이 동작하지 않고 전체 필드를 다 불러오는 것을 볼 수 있습니다. &lt;br /&gt;(스파크 몽고 커넥터 option 구성 참고 : &lt;a href=&quot;https://www.mongodb.com/ko-kr/docs/spark-connector/v10.1/batch-mode/batch-read-config/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.mongodb.com/ko-kr/docs/spark-connector/v10.1/batch-mode/batch-read-config/&lt;/a&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edqt2L/btsMEngxnIf/vHoxK0ns5nOJOwtMlCUkuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edqt2L/btsMEngxnIf/vHoxK0ns5nOJOwtMlCUkuK/img.png&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;924&quot; data-is-animation=&quot;false&quot; width=&quot;288&quot; height=&quot;346&quot; data-widthpercent=&quot;55.17&quot; style=&quot;width: 54.5292%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edqt2L/btsMEngxnIf/vHoxK0ns5nOJOwtMlCUkuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fedqt2L%2FbtsMEngxnIf%2FvHoxK0ns5nOJOwtMlCUkuK%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;770&quot; height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqlAQX/btsMEx4ipAU/w5QCmVPK532eEahSegWJ20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqlAQX/btsMEx4ipAU/w5QCmVPK532eEahSegWJ20/img.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;892&quot; data-is-animation=&quot;false&quot; width=&quot;321&quot; height=&quot;474&quot; style=&quot;width: 44.308%;&quot; data-widthpercent=&quot;44.83&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqlAQX/btsMEx4ipAU/w5QCmVPK532eEahSegWJ20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqlAQX%2FbtsMEx4ipAU%2Fw5QCmVPK532eEahSegWJ20%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;604&quot; height=&quot;892&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;✔️좌) 제대로 몽고커넥터 버전에 맞는 option명시 ✔️우) 몽고커넥터 무시하고 pipeline으로 잘못된 옵션 명시&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;반대로, &lt;b&gt;옛날 코드&lt;/b&gt;에서 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;aggregation.pipeline&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;라고 썼는데, &lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;최신 커넥터는 이걸 사용하지 않고 pipeline만 지원&lt;/span&gt;한다면, 그 값 역시 무시됩니다. 이처럼 &lt;b&gt;커넥터 버전이 바뀌면 옵션 이름이 변할 수 있고&lt;/b&gt;, 잘못된 옵션명을 넘기면 Spark는 별도 경고 없이 그대로 무시하는 경우가 대부분입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 예외: 일부 DataSource는 에러나 경고를 줄 수도 있다&lt;/h2&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;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;알 수 없는 옵션을 조용히 무시&lt;/span&gt;&amp;rdquo;하는 것은 아닙니다.&lt;br /&gt;&lt;b&gt;CSV&lt;/b&gt;(Spark 내장)처럼 일부 포맷은 특정 옵션이 잘못될 시 경고를 주거나, &amp;ldquo;필수 옵션이 빠졌다&amp;rdquo;며 에러를 낼 수도 있습니다.&lt;br /&gt;다만, 이런 처리는 &lt;b&gt;DataSource 구현체&lt;/b&gt;가 &amp;ldquo;어떤 옵션 목록을 검사할지&amp;rdquo; 결정하는 것이고, Spark Core 레벨에서 강제되는 것은 아닙니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 실제 현장에서 생기는 문제점&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;오탈자&lt;/b&gt; 문제Spark가 이를 무시해버리면, 파일을 헤더 없이 읽어 들이고, 사용자는 왜 컬럼명이나 데이터가 깨지는지 한동안 찾아야 하는 상황이 생길 수 있습니다.&lt;/li&gt;
&lt;li&gt;.option(&quot;heaer&quot;, &quot;true&quot;) # 'header'를 오타로 입력&lt;/li&gt;
&lt;li&gt;&lt;b&gt;버전 호환성&lt;/b&gt; 문제⭐️⭐️⭐️
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;문서/블로그 예시 코드&amp;rdquo;에서는 pipeline 옵션을 쓰라고 했는데, 내가 사용하는 커넥터 버전이 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;aggregation.pipeline&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;만 지원하면, 설정이 적용되지 않음.&lt;/li&gt;
&lt;li&gt;반대로 &lt;b&gt;최신 버전&lt;/b&gt;이 pipeline로 바뀌었는데, 옛날 옵션 키를 그대로 쓰면 작동 안 함.&lt;/li&gt;
&lt;li&gt;Spark 에러가 명확히 나오지 않으니, &lt;b&gt;사용자가 디버깅에 시간을 많이 쏟을&lt;/b&gt; 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 요약 및 권장사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Spark의 .option(...)는 전역 옵션 전달 방식&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spark는 (키, 값)을 받아서 DataSource로 넘길 뿐, 정의되지 않은 키에 대해 에러를 내지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커넥터 버전마다 옵션 키가 달라질 수 있음&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MongoDB Spark 커넥터 등에서는 pipeline, aggregation.pipeline, partitionerOptions.numPartitions 등 옵션 키가 버전에 따라 변경되기도 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;권장&lt;/b&gt;:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;공식 문서나 소스 코드&lt;/b&gt;를 확인해, &lt;b&gt;해당 버전&lt;/b&gt;에 맞는 옵션 키를 사용해야 합니다.&lt;/li&gt;
&lt;li&gt;오탈자 없이 정확히 입력하도록 주의하세요(특히 header vs heaer 같은 실수).&lt;/li&gt;
&lt;li&gt;로그나 코드에서 .option(...) 설정을 점검하고, &lt;b&gt;정말로 적용됐는지&lt;/b&gt; explain이나 결과값으로 검증해보는 습관이 필요합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;⭐️요약 정리⭐️&lt;br /&gt;✔️ &amp;ldquo;&lt;b&gt;정의되지 않은 옵션&lt;/b&gt; or &lt;b&gt;틀린 키&lt;/b&gt;&amp;rdquo;를 써도 Spark는 기본적으로 무시한다.&lt;br /&gt;✔️ 이로 인해 &amp;ldquo;&lt;b&gt;원하는 설정이 전혀 적용되지 않는&lt;/b&gt;&amp;rdquo; 문제가 발생할 수 있음.&lt;br /&gt;✔️ 버전이 달라지면 옵션 키 이름도 달라질 수 있으므로, 반드시 버전에 맞는 공식 문서를 확인하자.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 같이 보면 좋은 내용&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mongodb.com/docs/spark-connector/current/&quot;&gt;MongoDB Spark Connector 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spark.apache.org/docs/latest/&quot;&gt;Spark CSV/JSON/Parquet Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Spark DataFrameReader / DataFrameWriter .option() 사용 예시와 오탈자 관련 이슈&lt;/li&gt;
&lt;/ul&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;&amp;ldquo;왜 Spark는 옵션에 엉뚱한 키를 줘도 에러가 없나?&amp;rdquo;&lt;/b&gt; 및 &lt;b&gt;MongoDB 커넥터 버전별 옵션 차이&lt;/b&gt;가 빚어내는 문제를 정리해보았습니다. 개발 시엔 작은 오타 하나로도 큰 혼란이 발생하기 쉽기 때문에, 버전을 확인하고 옵션을 정확히 맞춰주는 게 중요하다는 것을 깨달았다. &lt;/p&gt;</description>
      <author>wonpick</author>
      <guid isPermaLink="true">https://pickwon.tistory.com/195</guid>
      <comments>https://pickwon.tistory.com/195#entry195comment</comments>
      <pubDate>Fri, 7 Mar 2025 16:52:57 +0900</pubDate>
    </item>
    <item>
      <title>Docker Jupyter 컨테이너에서 볼륨 마운트 시 권한 문제 발생 및 해결</title>
      <link>https://pickwon.tistory.com/194</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;매번 컨테이너 생성 할 때마다&amp;nbsp;&lt;b&gt;호스트(ec2-user)와 컨테이너 내부(jovyan)의 권한이 일치하지 않아 &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;b&gt;EC2에서 SSH로 접속한 ec2-user가 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨테이너 내부의 /home/jovyan/work 디렉토리 (호스트 디렉토리로 볼륨마운트) 에 대한 변경 권한이 없었기 때문&lt;/b&gt;에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발생한 문제로 어떻게 해결했는지 기록합니다.&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 문제의 핵심 정리&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2에서 SSH 접속 시, 기본적으로 &lt;b&gt;호스트의 ec2-user(UID=1000, GID=1000)로 로그인&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Docker 컨테이너 내부의 jovyan은 &lt;b&gt;UID=1000, GID=100(users)&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;/home/jovyan/work 디렉토리의 소유자가 root 로 되어 있어서 jovyan도 변경할 수 없는 상태였음.&lt;/li&gt;
&lt;li&gt;chown -R 1000 /home/jovyan을 실행했지만, work 디렉토리는 &lt;b&gt;root가 소유하고 있어서&lt;/b&gt; permission denied 오류 발생.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;즉,&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;컨테이너 내부에서 파일을 수정하려면 jovyan 권한이 필요하지만, work/의 소유자가 root라서 jovyan이 변경할 수 없었다.&amp;nbsp;&amp;nbsp;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;2736&quot; data-origin-height=&quot;2034&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1Zu2m/btsMyCroaTg/xmLNEd9N0TtkhvXA9CA6Y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1Zu2m/btsMyCroaTg/xmLNEd9N0TtkhvXA9CA6Y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1Zu2m/btsMyCroaTg/xmLNEd9N0TtkhvXA9CA6Y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1Zu2m%2FbtsMyCroaTg%2FxmLNEd9N0TtkhvXA9CA6Y1%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;409&quot; height=&quot;304&quot; data-origin-width=&quot;2736&quot; data-origin-height=&quot;2034&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 왜 EC2에서 접속했을 때 문제가 발생했을까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 SSH로 EC2에 접속하면 &lt;b&gt;호스트(ec2-user)에서 실행되는 작업이 컨테이너 내부의 jovyan과 같은 권한을 가질 수 없음&lt;/b&gt;.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) EC2에서 실행한 명령어의 권한 문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너를 이미 실행했다면 ec2의 uid가 어떻게 되어있는지 확인을 먼저 하고 소유자를 변경하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#확인
cat /etc/passwd #하게되면 ec2-user에 대한 uid 확인
#ec2-user:x:1000:1000:EC2&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;EC2에서 실행한 명령어는 ec2-user 권한을 가짐&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;하지만, &lt;b&gt;컨테이너 내부에서는 jovyan 권한을 사용하므로, root가 소유한 파일을 변경할 수 없음&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;그래서 permission denied 오류가 발생함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, EC2에서 실행하는 것 자체가 &lt;b&gt;외부 사용자(ec2-user)가 컨테이너 내부 사용자(jovyan)의 파일을 수정하려고 시도한 것&lt;/b&gt;과 같음.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 해결 방법과 원리&lt;/b&gt;&lt;/h3&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;&lt;b&gt;0.docker run 실행 시 --user 추가&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740644880230&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -it --user 1000:100 -v /home/ec2-user/spark/work:/home/jovyan/work spark-3.5.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;b&gt;1.컨테이너 내부에서 root 권한을 사용해 소유권을 변경&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740643802174&quot; class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;docker exec -it --user root &amp;lt;컨테이너 ID&amp;gt; bash
chown -R 1000 /home/jovyan
exit&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;--user root 옵션을 사용하여 컨테이너 내부에서 &lt;b&gt;root 권한&lt;/b&gt;을 얻음.&lt;/li&gt;
&lt;li&gt;root는 모든 파일을 변경할 수 있으므로, /home/jovyan/work의 소유권을 변경할 수 있음.&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. Dockerfile에서 미리 chown을 실행하여 컨테이너 생성 시 권한을 맞춤&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740643792797&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#RUN groupmod -g  users #그룹은 따로 설정 안함
USER root
RUN usermod -u 1000 jovyan
RUN chown -R 1000 /home/jovyan
USER jovyan&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컨테이너가 처음 실행될 때부터 권한이 정상적으로 설정&lt;/b&gt;됨.&lt;/li&gt;
&lt;li&gt;다만 빌드 시점에 chown이 실행되므로 볼륨 마운트는 컨테이너 실행 시점에 적용되기 때문에 컨테이너가 실행되면서 다시 호스트 디렉토리가 덮어씌워지면서 root로 보일 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 결론&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  최종 원인&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2에서 SSH로 접속한 ec2-user가 컨테이너 내부의 /home/jovyan/work 디렉토리를 수정하려 했지만,&lt;br /&gt;해당 디렉토리는 &lt;b&gt;root가 소유&lt;/b&gt;하고 있어서 jovyan도 수정할 수 없었음.&lt;/li&gt;
&lt;li&gt;결국, &lt;b&gt;컨테이너 내부에서 root 권한을 사용해 소유권을 변경해야 했음&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;혹은 docker run 할때 --user 에다가 uid 를 전달해줘도 됨.&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;922&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZRymK/btsMzsIrOQ4/G2StIiuDPoUOPXRLYcz3K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZRymK/btsMzsIrOQ4/G2StIiuDPoUOPXRLYcz3K1/img.png&quot; data-alt=&quot;root -&amp;amp;gt; jovyan 변경되는걸 보면 됨. uid 변경&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZRymK/btsMzsIrOQ4/G2StIiuDPoUOPXRLYcz3K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZRymK%2FbtsMzsIrOQ4%2FG2StIiuDPoUOPXRLYcz3K1%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;608&quot; height=&quot;169&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;root -&amp;gt; jovyan 변경되는걸 보면 됨. uid 변경&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  핵심 개념&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;EC2에서 실행하는 명령어는 기본적으로 ec2-user 권한을 가짐&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컨테이너 내부의 jovyan과 ec2-user는 UID가 같아도 권한은 다름&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;root가 소유한 디렉토리는 jovyan이 직접 변경할 수 없음&lt;/b&gt; &amp;rarr; root로 변경 후 chown 실행 필요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Dockerfile에서 미리 chown을 실행하면 실행할 때마다 권한 문제를 해결할 필요 없음&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>개발</category>
      <author>wonpick</author>
      <guid isPermaLink="true">https://pickwon.tistory.com/194</guid>
      <comments>https://pickwon.tistory.com/194#entry194comment</comments>
      <pubDate>Thu, 27 Feb 2025 18:24:00 +0900</pubDate>
    </item>
    <item>
      <title>코드트리 사용 후기: 다시 만난 코드트리 (작년과 어떤 점이 달라졌을까?)</title>
      <link>https://pickwon.tistory.com/193</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz6Pmi/btsMbWYkB9H/IyYruRM65Xdt3rYk6cmAi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz6Pmi/btsMbWYkB9H/IyYruRM65Xdt3rYk6cmAi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz6Pmi/btsMbWYkB9H/IyYruRM65Xdt3rYk6cmAi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz6Pmi%2FbtsMbWYkB9H%2FIyYruRM65Xdt3rYk6cmAi0%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;335&quot; height=&quot;193&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;020&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/020.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/020.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;코딩테스트를 준비하며 효과적인 학습 도구를 찾던 중, 작년에 처음 코드트리를 접했습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;원하는 난이도의 문제를 쉽게 찾고, 상세한 해설을 제공받을 수 있어 인상 깊었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;작년 1기에 이어 이번에 &lt;/span&gt;&lt;span&gt;코드트리 X 글또 블로그 챌린지 2기&lt;/span&gt;&lt;span&gt;에 참여하게 되어 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;한 달간 코드트리를 집중적으로 활용하며 &lt;/span&gt;&lt;span&gt;서비스의 변화를 체험해 보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;학습 동기를 부여하는 XP 시스템&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;코드트리의 XP 시스템은 게임처럼 문제를 풀면서 경험치를 쌓고 목표를 달성하는 방식입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;학습 목표를 설정하고 이를 충족하면 성취감을 느낄 수 있어 꾸준한 학습을 유도하는 데 효과적이었습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-09 오후 9.32.55.png&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMfblB/btsMcIykzMr/DaRpKF4iG8yNxqlFDrNIk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMfblB/btsMcIykzMr/DaRpKF4iG8yNxqlFDrNIk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMfblB/btsMcIykzMr/DaRpKF4iG8yNxqlFDrNIk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMfblB%2FbtsMcIykzMr%2FDaRpKF4iG8yNxqlFDrNIk1%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;335&quot; height=&quot;189&quot; data-filename=&quot;스크린샷 2025-02-09 오후 9.32.55.png&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&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;896&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIGqfb/btsMbxdCUuH/gkM8eDcMVo8YtXVpwgoqBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIGqfb/btsMbxdCUuH/gkM8eDcMVo8YtXVpwgoqBK/img.png&quot; data-alt=&quot;연속 학습 시 나무가 될 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIGqfb/btsMbxdCUuH/gkM8eDcMVo8YtXVpwgoqBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIGqfb%2FbtsMbxdCUuH%2FgkM8eDcMVo8YtXVpwgoqBK%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;371&quot; height=&quot;174&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;연속 학습 시 나무가 될 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;맞춤형 커리큘럼 제공&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;코드트리는 사용자의 실력에 맞춘 6단계의 Trail(커리큘럼)을 제공합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;1590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKZa74/btsMbDdx0nu/KDozx8pAybk2EGWfbx0Nhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKZa74/btsMbDdx0nu/KDozx8pAybk2EGWfbx0Nhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKZa74/btsMbDdx0nu/KDozx8pAybk2EGWfbx0Nhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKZa74%2FbtsMbDdx0nu%2FKDozx8pAybk2EGWfbx0Nhk%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;486&quot; height=&quot;358&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;1590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Novice Low (Trail 1)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 기초 문법 학습&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Novice Mid (Trail 2)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 기초 구현력 및 디버깅 연습&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Novice High (Trail 3)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 알고리즘 및 자료구조 객관식 문제 학습&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Intermediate Low (Trail 4)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: BFS, DFS, DP 등의 기초 알고리즘 학습&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Intermediate Mid (Trail 5)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Two Pointer, 이분 탐색 등의 고급 알고리즘 학습&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Intermediate High (Trail 6)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Tree, 위상 정렬 등 심화 알고리즘 학습&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;각 단계별로 체계적인 학습이 가능하며, 사용자가 본인의 취약한 부분을 보완할 수 있도록 유도합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;저 역시 DP 문제에서 어려움을 겪었기 때문에 &lt;/span&gt;&lt;span&gt;Intermediate Low&lt;/span&gt;&lt;span&gt; 커리큘럼을 선택해 실력을 향상시킬 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pgnEx/btsMcyo2Clb/50sSsNL0Nl3QQhXaF5W1jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pgnEx/btsMcyo2Clb/50sSsNL0Nl3QQhXaF5W1jk/img.png&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;528&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.3946%; margin-right: 10px;&quot; data-widthpercent=&quot;57.06&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pgnEx/btsMcyo2Clb/50sSsNL0Nl3QQhXaF5W1jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpgnEx%2FbtsMcyo2Clb%2F50sSsNL0Nl3QQhXaF5W1jk%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;1046&quot; height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cS8NId/btsMbcHzpvD/PSTx1zhdpK7kzcOCuuzES0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cS8NId/btsMbcHzpvD/PSTx1zhdpK7kzcOCuuzES0/img.png&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;884&quot; data-is-animation=&quot;false&quot; width=&quot;481&quot; height=&quot;323&quot; data-widthpercent=&quot;42.94&quot; style=&quot;width: 42.4427%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cS8NId/btsMbcHzpvD/PSTx1zhdpK7kzcOCuuzES0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcS8NId%2FbtsMbcHzpvD%2FPSTx1zhdpK7kzcOCuuzES0%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;1318&quot; height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;체계적인 학습 환경과 GitHub 연동&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;코드트리는 각 커리큘럼을 Chapter와 Lesson 단위로 세분화하여 보다 체계적인 학습을 지원합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;개념 학습 &amp;rarr; 문제 풀이 &amp;rarr; 실력 점검&lt;/b&gt;이라는 흐름을 통해 사용자의 문제 해결 능력을 향상시키는 방식이 인상적이었습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또한, GitHub 연동 기능을 활용하면 풀이 기록을 자동으로 저장할 수 있어 복습이 훨씬 편리했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgD83h/btsMa9KNfRd/tc92VaJVkkPc1sNAgYEsjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgD83h/btsMa9KNfRd/tc92VaJVkkPc1sNAgYEsjk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;692&quot; data-filename=&quot;스크린샷 2025-02-09 오후 9.49.59.png&quot; width=&quot;428&quot; height=&quot;225&quot; style=&quot;width: 46.2828%; margin-right: 10px;&quot; data-widthpercent=&quot;46.83&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgD83h/btsMa9KNfRd/tc92VaJVkkPc1sNAgYEsjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgD83h%2FbtsMa9KNfRd%2Ftc92VaJVkkPc1sNAgYEsjk%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;1318&quot; height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4RoYP/btsMcj6NRov/yv8ycRt3bbozLs5d6n17Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4RoYP/btsMcj6NRov/yv8ycRt3bbozLs5d6n17Tk/img.png&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;590&quot; data-is-animation=&quot;false&quot; width=&quot;460&quot; height=&quot;213&quot; data-widthpercent=&quot;53.17&quot; style=&quot;width: 52.5544%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4RoYP/btsMcj6NRov/yv8ycRt3bbozLs5d6n17Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4RoYP%2FbtsMcj6NRov%2Fyv8ycRt3bbozLs5d6n17Tk%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;1276&quot; height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;UI/UX 개선과 목표 기업 실력 진단&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1년 전과 비교해 코드트리는 UI/UX 측면에서 상당한 개선이 있었습니다. 더욱 직관적인 디자인과 다국어 지원(한국어, 영어)이 추가되었으며, 목표 기업 코딩테스트 실력 진단 기능도 새롭게 도입되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&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;2052&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0NTOd/btsMcfcijz1/WrrhX3sKCMptCpRp7iKQlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0NTOd/btsMcfcijz1/WrrhX3sKCMptCpRp7iKQlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0NTOd/btsMcfcijz1/WrrhX3sKCMptCpRp7iKQlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0NTOd%2FbtsMcfcijz1%2FWrrhX3sKCMptCpRp7iKQlK%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;598&quot; height=&quot;153&quot; data-origin-width=&quot;2052&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;삼성, 구글 등의 기업 기준에 맞춘 실력 진단을 받을 수 있으며, 이를 기반으로 맞춤형 학습 코스까지 제공됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;아쉬운 점&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;전반적으로 만족스러웠지만, 몇 가지 개선이 필요한 부분도 있었습니다. &lt;/span&gt;&lt;span&gt;문제 사이를 이동할 때 로딩 시간이 다소 길어지는 경우가 있었으며, &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일부 페이지에서는 영문 설명이 나타나는 등의 자잘한 버그도 있었습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또한, 기존의 업적 뱃지 시스템이 사라진 점은 게이미피케이션 요소를 즐기던 사용자들에게 아쉬운 부분이 될 수 있을 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;뿐만 아니라 UI가 변경되면서 이전보다는 직관적으로 원하는 부분을 찾기가 힘들어 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사이트내에서 계속 헤매게 되었던 적이 많아졌던 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;마무리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;한 달 동안 코드트리를 활용하면서 코딩 테스트 실력을 보다 체계적으로 향상시킬 수 있었습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;특히 맞춤형 커리큘럼과 GitHub 연동 기능이 학습의 지속성을 유지하는 데 큰 도움이 되었습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;앞으로도 남은 한 달 동안 코드트리를 적극 활용하여 부족한 부분을 확실히 보완할 계획입니다. 코드트리는 코딩 테스트를 준비하는 이들에게 강력한 학습 도구가 될 것이라 확신합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;남은 한달동안도 더 1월보다 더 열심히 문제를 풀어 발전된 모습을 기록해보고 싶습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>STUDY/Algorithm</category>
      <author>wonpick</author>
      <guid isPermaLink="true">https://pickwon.tistory.com/193</guid>
      <comments>https://pickwon.tistory.com/193#entry193comment</comments>
      <pubDate>Sun, 9 Feb 2025 21:52:21 +0900</pubDate>
    </item>
    <item>
      <title>EMR Serverless와 Step Functions으로 Spark ETL 구축하기</title>
      <link>https://pickwon.tistory.com/192</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;아래 글은 EMR Serverless + Step Functions: Python to Spark&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;h3 data-ke-size=&quot;size23&quot;&gt;1. 개요&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;물리 서버에 구축된 jupyter hub환경&lt;/span&gt;에서 python으로 데이터 처리 후 아래와 같은 구조로 작업을 진행했다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FZM4z/btsL48cJToR/XUIZsHhBJbmm95oKuQSac0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FZM4z/btsL48cJToR/XUIZsHhBJbmm95oKuQSac0/img.png&quot; data-alt=&quot;기존 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FZM4z/btsL48cJToR/XUIZsHhBJbmm95oKuQSac0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFZM4z%2FbtsL48cJToR%2FXUIZsHhBJbmm95oKuQSac0%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;529&quot; height=&quot;206&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;484&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, Python에서도 multiprocessing 같은 라이브러리를 활용하면 &lt;b&gt;멀티프로세싱을 통해 병렬 처리를 수행할 수 있다.&lt;/b&gt; 하지만, 이러한 방식은 기본적으로 &lt;b&gt;단일 머신의 CPU 리소스에 &lt;/b&gt;제한 되기 때문에, &lt;b&gt;대량의 데이터를 다룰 때는 결국 클러스터 기반의 분산 처리가 필요&lt;/b&gt;했고, 이에 따라 Spark로의 마이그레이션을 고려하게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 Spark로의 전환을 고민할 때는 &lt;b&gt;EMR Serverless를 바로 선택한 것이 아니라, 기존 EMR(EC2 기반)을 먼저 도입하여 Spark 마이그레이션을 진행해보자&lt;/b&gt;는 접근 방식을 택했다. 기존 EMR을 활용하면 EC2 인스턴스를 사용하여 &lt;b&gt;YARN 기반의 Spark 클러스터를 직접 운영할 수 있고, 기존 Python 코드를 점진적으로 PySpark로 변환하는 작업을 진행&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-origin-width=&quot;1312&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAXigz/btsL4GufOjA/eAI3y5pvnaEZBi5F0Il4P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAXigz/btsL4GufOjA/eAI3y5pvnaEZBi5F0Il4P1/img.png&quot; data-alt=&quot;첫 구상안&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAXigz/btsL4GufOjA/eAI3y5pvnaEZBi5F0Il4P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAXigz%2FbtsL4GufOjA%2FeAI3y5pvnaEZBi5F0Il4P1%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;528&quot; height=&quot;300&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;746&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;그러나 EMR을 운영하면서 몇 가지 문제점이 나타났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 첫 번째로,&lt;b&gt; 비용 문제&lt;/b&gt;였다. EMR(EC2 기반)에서는 클러스터가 항상 실행되고 있어야 했기 때문에, 작업을 수행하지 않는 유휴 시간에도 인스턴스 비용이 지속적으로 발생했다. 또한, 스팟 인스턴스를 활용하여 비용을 절감할 수는 있었지만, &lt;b&gt;장기적으로 볼 때 EMR을 계속 운영하는 것은 비용적으로 부담이 되는 구조였다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 두 번째 문제는 &lt;b&gt;운영 관리의 복잡성&lt;/b&gt;이었다. EMR 클러스터는 &lt;b&gt;Spark 설정, YARN 리소스 관리, Auto-Scaling 설정 등 여러 가지 튜닝이 필요&lt;/b&gt;했다. 작은 규모에서는 문제가 되지 않았지만, Spark Job을 여러 개 실행하거나 데이터 처리량이 변동될 경우 적절한 리소스 관리를 위해 클러스터를 조정해야 하는 부담이 컸다.&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: #f6e199;&quot;&gt;EMR Serverless&lt;/span&gt;로 전환을 결정했다.&lt;/b&gt; EMR Serverless는 클러스터를 직접 관리할 필요 없이, &lt;b&gt;Spark Job을 실행할 때 필요한 만큼만 리소스를 할당하고 작업이 끝나면 자동으로 리소스를 해제하는 방식&lt;/b&gt;을 제공했다. 덕분에 &lt;b&gt;비용이 사용한 만큼만 과금되며, 관리 부담도 최소화할 수 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, Step Functions을 활용하여 EMR Serverless + Step Functions + Spark로의 전환으로&amp;nbsp;&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;이번 블로그 시리즈에서는 기존 Python 기반의 데이터 처리 방식을 EMR Serverless 환경으로 마이그레이션하는 과정 중에서 EMR Serverless를 StepFunction에서 실행하고 그 과정에서 고려해야 할 사항 등을 정리해볼 예정이다.&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;2. EMR&amp;nbsp;Serverless에서&amp;nbsp;Spark&amp;nbsp;구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로&amp;nbsp;내가&amp;nbsp;spark&amp;nbsp;작업을&amp;nbsp;하면서&amp;nbsp;헷갈렸던&amp;nbsp;부분인&amp;nbsp;deploy&amp;nbsp;mode에&amp;nbsp;대해서&amp;nbsp;먼저&amp;nbsp;정리를&amp;nbsp;하고&amp;nbsp;가겠다!&amp;nbsp;Spark에서는&amp;nbsp;deploy-mode를&amp;nbsp;선택하는&amp;nbsp;것이&amp;nbsp;매우&amp;nbsp;중요한데,&amp;nbsp;이는&amp;nbsp;Driver의&amp;nbsp;실행&amp;nbsp;위치와&amp;nbsp;클러스터&amp;nbsp;자원&amp;nbsp;할당&amp;nbsp;방식이&amp;nbsp;결정되기&amp;nbsp;때문이다.&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;로컬&amp;nbsp;환경에서&amp;nbsp;Standalone&amp;nbsp;Mode로&amp;nbsp;Client&amp;nbsp;Mode로&amp;nbsp;실행하여&amp;nbsp;디버깅한&amp;nbsp;뒤,&amp;nbsp;최종적으로&amp;nbsp;EMR&amp;nbsp;Serverless에서&amp;nbsp;Spark&amp;nbsp;Job을&amp;nbsp;실행하는&amp;nbsp;방식으로&amp;nbsp;운영&amp;nbsp;중인데,&amp;nbsp;emr&amp;nbsp;serverless에서는&amp;nbsp;당연하게도&amp;nbsp;&amp;nbsp;Client&amp;nbsp;Mode&amp;nbsp;지원이&amp;nbsp;되지&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;이유는 Driver를 사용자 머신에서 실행하는 방식(Client Mode)이 서버리스 환경과 맞지 않기 때문.&lt;/li&gt;
&lt;li&gt;spark-submit을 실행할 때 기본적으로 Cluster Mode로 동작하며, Driver가 EMR Serverless 내부에서 실행됨.&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;즉, EMR Serverless에서는 모든 것이 Worker로 실행되며, Worker 내부에서 Driver와 Executor가 동작하는 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Master Node를 관리할 필요가 없으며 그에 따른 YARN, Kubernetes, Standalone 등 클러스터 매니저 설정을 신경 쓸 필요 없다.&amp;nbsp;&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;3. EMR Serverless, Step Functions에서 실행하기 위해 필요한 조건 - IAM Role 설정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;State&amp;nbsp;Machine&amp;nbsp;IAM&amp;nbsp;Role
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적 : Step Functions 실행 및 EMR Serverless 실행 관리&amp;nbsp;&lt;/li&gt;
&lt;li&gt;참고 문서:&lt;br /&gt;&lt;a href=&quot;https://docs.aws.amazon.com/emr/latest/EMR-Serverless-UserGuide/setting-up.html#setting-up-iam&quot;&gt; https://docs.aws.amazon.com/emr/latest/EMR-Serverless-UserGuide/setting-up.html#setting-up-iam&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1738502348636&quot; class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#Action부분만 기재 (참고용)
&quot;states:StartExecution&quot;,
&quot;states:DescribeExecution&quot;,
&quot;states:StopExecution&quot;,
&quot;states:GetExecutionHistory&quot;,
&quot;emr-serverless:CreateApplication&quot;,
&quot;emr-serverless:StartApplication&quot;,
&quot;emr-serverless:StartJobRun&quot;,
&quot;emr-serverless:GetJobRun&quot;,
&quot;emr-serverless:StopApplication&quot;,
&quot;dynamodb:ImportTable&quot;,
&quot;dynamodb:DescribeTable&quot;,
&quot;dynamodb:ListTables&quot;,
&quot;logs:CreateLogGroup&quot;,
&quot;logs:CreateLogStream&quot;,
&quot;logs:PutLogEvents&quot;,
&quot;iam:PassRole&quot;&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;Step&amp;nbsp;Functions&amp;nbsp;Execution&amp;nbsp;Role
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적 : EMR Serverless에서 Spark Job 실행 및 S3/DynamoDB 접근&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1738502389484&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#Action부분만 기재 (참고용)
&quot;s3:GetObject&quot;,
&quot;s3:PutObject&quot;,
&quot;s3:ListBucket&quot;,
&quot;dynamodb:PutItem&quot;,
&quot;dynamodb:GetItem&quot;,
&quot;dynamodb:Scan&quot;,
&quot;dynamodb:Query&quot;,
&quot;logs:CreateLogGroup&quot;,
&quot;logs:CreateLogStream&quot;,
&quot;logs:PutLogEvents&quot;&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;(선택) stepfuntion에서 dynamo import table을 위한
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;+ logs:GetLogEvents&lt;/li&gt;
&lt;li&gt;참고 문서:&lt;br /&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S3DataImport.Requesting.html&quot;&gt;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S3DataImport.Requesting.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.&amp;nbsp; Step&amp;nbsp;Functions&amp;nbsp;워크플로우&amp;nbsp;설계&lt;/h3&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: #f6e199;&quot;&gt;(1) 기본 템플릿 생성&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X8gyo/btsL4wyAzV8/dKmYuD3BChGq7xF5axtKok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X8gyo/btsL4wyAzV8/dKmYuD3BChGq7xF5axtKok/img.png&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;1048&quot; data-is-animation=&quot;false&quot; style=&quot;width: 53.9033%; margin-right: 10px;&quot; data-widthpercent=&quot;54.54&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X8gyo/btsL4wyAzV8/dKmYuD3BChGq7xF5axtKok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX8gyo%2FbtsL4wyAzV8%2FdKmYuD3BChGq7xF5axtKok%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;1694&quot; height=&quot;1048&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pioVm/btsL5BFF4zm/72Lo1QIwzg3ZOWTxOZz2C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pioVm/btsL5BFF4zm/72Lo1QIwzg3ZOWTxOZz2C1/img.png&quot; data-origin-width=&quot;1528&quot; data-origin-height=&quot;1134&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.9339%;&quot; data-widthpercent=&quot;45.46&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pioVm/btsL5BFF4zm/72Lo1QIwzg3ZOWTxOZz2C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpioVm%2FbtsL5BFF4zm%2F72Lo1QIwzg3ZOWTxOZz2C1%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;1528&quot; height=&quot;1134&quot;/&gt;&lt;/span&gt;&lt;/div&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;예시로 emr serverless 작업 실행을 선택해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCDq3M/btsL3xLHcVc/GtcwZRDCNy9mEqncoAkuP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCDq3M/btsL3xLHcVc/GtcwZRDCNy9mEqncoAkuP0/img.png&quot; data-origin-width=&quot;2600&quot; data-origin-height=&quot;1388&quot; data-is-animation=&quot;false&quot; style=&quot;width: 51.7388%; margin-right: 10px;&quot; data-widthpercent=&quot;52.35&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCDq3M/btsL3xLHcVc/GtcwZRDCNy9mEqncoAkuP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCDq3M%2FbtsL3xLHcVc%2FGtcwZRDCNy9mEqncoAkuP0%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;2600&quot; height=&quot;1388&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o7wdZ/btsL5z8Xyys/BiHOSTwEnkF8TYzprhvvE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o7wdZ/btsL5z8Xyys/BiHOSTwEnkF8TYzprhvvE0/img.png&quot; data-origin-width=&quot;2036&quot; data-origin-height=&quot;1194&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.0984%;&quot; data-widthpercent=&quot;47.65&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o7wdZ/btsL5z8Xyys/BiHOSTwEnkF8TYzprhvvE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo7wdZ%2FbtsL5z8Xyys%2FBiHOSTwEnkF8TYzprhvvE0%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;2036&quot; height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;/div&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;바로 생성하려고 하면 executionrole이 설정이 안되어있다고 하는데 위에서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EMR Serverless에서 Spark Job 실행 및 S3/DynamoDB 접근을 위해 생성한 role을 기재해준다.&amp;nbsp;&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;1686&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcsymd/btsL4JqVvFj/z1ECBjiYIH91GLtLs4R0Zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcsymd/btsL4JqVvFj/z1ECBjiYIH91GLtLs4R0Zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcsymd/btsL4JqVvFj/z1ECBjiYIH91GLtLs4R0Zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcsymd%2FbtsL4JqVvFj%2Fz1ECBjiYIH91GLtLs4R0Zk%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;544&quot; height=&quot;267&quot; data-origin-width=&quot;1686&quot; data-origin-height=&quot;828&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;기존에 stepfunction 실행을 위한 role을 만들어놨다면 선택해주고 그게 아니라면 생성도 가능하다.&amp;nbsp;&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;2086&quot; data-origin-height=&quot;1310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Au89P/btsL3k0ddgR/W6xgd3ACxwFFq7BVQW3K4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Au89P/btsL3k0ddgR/W6xgd3ACxwFFq7BVQW3K4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Au89P/btsL3k0ddgR/W6xgd3ACxwFFq7BVQW3K4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAu89P%2FbtsL3k0ddgR%2FW6xgd3ACxwFFq7BVQW3K4k%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;500&quot; height=&quot;314&quot; data-origin-width=&quot;2086&quot; data-origin-height=&quot;1310&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;최종적으로 생성한 stepfunction이다.&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;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;(2) 실제 설계한 워크플로우 예시&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r1a20/btsL45tAw6c/kmkbkL2O8rqdsEd0RcwMfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r1a20/btsL45tAw6c/kmkbkL2O8rqdsEd0RcwMfK/img.png&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;1230&quot; data-is-animation=&quot;false&quot; style=&quot;width: 53.058%; margin-right: 10px;&quot; data-widthpercent=&quot;53.68&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r1a20/btsL45tAw6c/kmkbkL2O8rqdsEd0RcwMfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr1a20%2FbtsL45tAw6c%2FkmkbkL2O8rqdsEd0RcwMfK%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;1930&quot; height=&quot;1230&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhPD4j/btsL466699I/R6X3SyER4KWeEpDrqsKx8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhPD4j/btsL466699I/R6X3SyER4KWeEpDrqsKx8k/img.png&quot; data-origin-width=&quot;1760&quot; data-origin-height=&quot;1300&quot; data-is-animation=&quot;false&quot; style=&quot;width: 45.7792%;&quot; data-widthpercent=&quot;46.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhPD4j/btsL466699I/R6X3SyER4KWeEpDrqsKx8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhPD4j%2FbtsL466699I%2FR6X3SyER4KWeEpDrqsKx8k%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;1760&quot; height=&quot;1300&quot;/&gt;&lt;/span&gt;&lt;/div&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;참고로 step function에서는 InitialCapacity를 명시적으로 설정하지 않으면 기본값이 할당된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 스토리지는 20GB 이하로 설정 시 에러가 나니 무조건 20GB 이상으로 설정해야한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EMR Serverless의 InitialCapacity 기본값&lt;/li&gt;
&lt;/ul&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;text-align: center;&quot;&gt;&lt;b&gt;Component&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;기본 vCPU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;기본 Memory&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;기본 Disk&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;Worker&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;Driver / Executor&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;4 vCPU&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;16 GB&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;20 GB&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;1&lt;/b&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;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;주요 제한 사항&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고:&lt;a href=&quot;https://aws.amazon.com/ko/emr/faqs/?nc=sn&amp;amp;loc=5&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/emr/faqs/?nc=sn&amp;amp;loc=5&lt;/a&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&gt;CPU(vCPU)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 작업자는 &lt;b&gt;1, 2, 4, 8, 16 vCPU 중 하나만 설정 가능&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리(GB)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 개수에 따라 &lt;b&gt;최소/최대 메모리 설정 가능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;1 vCPU &amp;rarr; 최소 2GB, 최대 8GB (1GB 단위 증분)&lt;/li&gt;
&lt;li&gt;16 vCPU &amp;rarr; 최소 32GB, 최대 120GB (8GB 단위 증분)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디스크(Disk, GB)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;최소 20GB, 최대 200GB&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;기본적으로 &lt;b&gt;20GB는 무료이며, 20GB 초과 부분만 비용 부과됨&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;&lt;span style=&quot;background-color: #f6e199; color: #333333; text-align: start;&quot;&gt;(3) Step Functions JSON 정의 예시 (job run 부분만)&amp;nbsp;&lt;br /&gt;&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;414&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zNIFX/btsL45f3wxu/lM65x6QBFXZnjvKuMMDowK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zNIFX/btsL45f3wxu/lM65x6QBFXZnjvKuMMDowK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zNIFX/btsL45f3wxu/lM65x6QBFXZnjvKuMMDowK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzNIFX%2FbtsL45f3wxu%2FlM65x6QBFXZnjvKuMMDowK%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;180&quot; height=&quot;692&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;692&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;아래 부분에서 기본 템플릿과 다른 점은 job 실패 시 바로 stop application으로 가도록 설정한 부분이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 실행할 spark job 스크립트를 s3에 업로드 후 해당 경로를 EntryPoint에 입력해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&quot;EntryPoint&quot;: &quot;s3://S3-BUCKET/scripts/my_spark_job.py&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;뿐만 아니라 중간에 잡이 실패할 경우 디버깅이 필요하므로 log를 기록할 경로도 기재해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3에 로그를 기록하는 기본 옵션을 택했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;LogUri&quot;: &quot;s3://S3-BUCKET/logs/&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;(참고로 Spark 작업에서 job의 에러 로그 및 print() 되는 출력은 항상 Driver에서 확인해야하므로 SPARK_DRIVER/stdout.gz 에서 확인하면 된다.)&lt;/p&gt;
&lt;pre id=&quot;code_1738504627761&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    &quot;B2B_GP_Count_Processing&quot;: {
      &quot;Type&quot;: &quot;Task&quot;,
      &quot;Resource&quot;: &quot;arn:aws:states:::emr-serverless:startJobRun.sync&quot;,
      &quot;Arguments&quot;: {
        &quot;ApplicationId&quot;: &quot;{% $ApplicationId %}&quot;,
        &quot;ExecutionRoleArn&quot;: &quot;arn:aws:iam::YOUR_ACCOUNT_ID:role/emr_serverless_execution_role&quot;: {
          &quot;SparkSubmit&quot;: {
            &quot;EntryPoint&quot;: &quot;s3://S3-BUCKET/scripts/my_spark_job.py&quot;
          }
        },
        &quot;ConfigurationOverrides&quot;: {
          &quot;MonitoringConfiguration&quot;: {
            &quot;S3MonitoringConfiguration&quot;: {
              &quot;LogUri&quot;: &quot;s3://S3-BUCKET/logs/&quot;
            }
          }
        }
      },
      &quot;Assign&quot;: {
        &quot;FirstJobId&quot;: &quot;{% $states.result.JobRunId %}&quot;
      },
      &quot;Next&quot;: &quot;ImportTable&quot;,
      &quot;Catch&quot;: [
        {
          &quot;ErrorEquals&quot;: [
            &quot;States.TaskFailed&quot;
          ],
          &quot;Next&quot;: &quot;Stop Application&quot;,
          &quot;Comment&quot;: &quot;Stop application on any occasion&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;5. 로컬에서&amp;nbsp;Step&amp;nbsp;Functions&amp;nbsp;자동&amp;nbsp;배포&amp;nbsp;및&amp;nbsp;실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 정리를 했지만 로컬에서 stepfunction을 바로바로 수정 실행 할 수 있게 deploy.sh 라는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트를 만들어 조금 더 빠르게 반영될 수 있게 하였다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738507622817&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

# ✅ S3 버킷 및 상태 머신 JSON 파일 설정
BUCKET_NAME=&quot;your-s3-bucket/scripts&quot;
JSON_FILE=&quot;your_state_machine.asl.json&quot;
STATE_MACHINE_ARN=&quot;arn:aws:states:your-region:your-account-id:stateMachine:your-state-machine&quot;

# ✅ S3 Key Prefix 및 DynamoDB 테이블명 설정
end_ym=$(date -d &quot;$(date +%Y-%m-01) -2 month&quot; +%Y%m)  # 현재 날짜 기준 2달 전 연월 (YYYYMM)
mmdd=$(date +%m%d)  # 현재 날짜 (MMDD)
NEW_S3_KEY_PREFIX=&quot;dynamo_import_path/gzip_${end_ym}&quot;
NEW_TABLE_NAME=&quot;dynamo_table_${end_ym}v${mmdd}&quot;

# ✅ 인자값 확인 (PySpark 작업이 2개 실행되므로 스크립트 2개가 필요함)
if [ &quot;$#&quot; -ne 2 ]; then
    echo &quot;Usage: ./deploy.sh &amp;lt;PYSPARK_SCRIPT_1&amp;gt; &amp;lt;PYSPARK_SCRIPT_2&amp;gt;&quot;
    exit 1
fi

PYSPARK_SCRIPT_1=$1
PYSPARK_SCRIPT_2=$2

# ✅ PySpark 스크립트 S3 업로드
echo &quot;Uploading PySpark scripts to S3...&quot;
aws s3 cp &quot;/path/to/your/scripts/$PYSPARK_SCRIPT_1&quot; &quot;s3://$BUCKET_NAME/&quot;
aws s3 cp &quot;/path/to/your/scripts/$PYSPARK_SCRIPT_2&quot; &quot;s3://$BUCKET_NAME/&quot;

# ✅ 상태 머신 JSON 업데이트
echo &quot;Updating state machine JSON file...&quot;

# 1️⃣ 첫 번째 PySpark 스크립트 (`B2B_GP_Processing` 단계 업데이트)
sed -i '/&quot;B2B_GP_Processing&quot;/,/^ *}/ {
  s|&quot;EntryPoint&quot;: *&quot;s3://[^&quot;]*\.py&quot;|\&quot;EntryPoint\&quot;: \&quot;s3://'&quot;$BUCKET_NAME&quot;'/'&quot;$PYSPARK_SCRIPT_1&quot;'\&quot;|
}' &quot;$JSON_FILE&quot;

# 2️⃣ 두 번째 PySpark 스크립트 (`B2B_GP_Save_DynamoDBjson` 단계 업데이트)
sed -i '/&quot;B2B_GP_Save_DynamoDBjson&quot;/,/^ *}/ {
  s|&quot;EntryPoint&quot;: *&quot;s3://[^&quot;]*\.py&quot;|\&quot;EntryPoint\&quot;: \&quot;s3://'&quot;$BUCKET_NAME&quot;'/'&quot;$PYSPARK_SCRIPT_2&quot;'\&quot;|
}' &quot;$JSON_FILE&quot;

# 3️⃣ DynamoDB 테이블 및 S3 경로 업데이트
sed -i &quot;s|\&quot;S3KeyPrefix\&quot;: \&quot;.*\&quot;|\&quot;S3KeyPrefix\&quot;: \&quot;${NEW_S3_KEY_PREFIX}\&quot;|&quot; &quot;$JSON_FILE&quot;
sed -i &quot;s|\&quot;TableName\&quot;: \&quot;.*\&quot;|\&quot;TableName\&quot;: \&quot;${NEW_TABLE_NAME}\&quot;|&quot; &quot;$JSON_FILE&quot;

# ✅ Step Functions 상태 머신 업데이트
echo &quot;Updating Step Functions state machine...&quot;
aws stepfunctions update-state-machine \
    --state-machine-arn &quot;$STATE_MACHINE_ARN&quot; \
    --definition file://&quot;$JSON_FILE&quot;

# ✅ Step Function 실행
echo &quot;Starting Step Function execution...&quot;
aws stepfunctions start-execution --state-machine-arn &quot;$STATE_MACHINE_ARN&quot;

echo &quot;Deployment complete!&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 step function으로 설계했던 것을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;airflow를 활용하여 워크플로우를 설계하고 스케쥴링 될 수 있게 제작을 진행하여 step function과&amp;nbsp; 위 deploy.sh는 더이상 사용하지는 않지만&amp;nbsp;기록용으로 기재한다.&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;6.&lt;span&gt;&amp;nbsp;&lt;/span&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EMR&amp;nbsp;Serverless와&amp;nbsp;Step&amp;nbsp;Functions을&amp;nbsp;활용해서&amp;nbsp;PySpark&amp;nbsp;데이터&amp;nbsp;파이프라인을&amp;nbsp;자동화하는&amp;nbsp;과정을&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;EMR Serverless를 활용하면 클러스터를 직접 관리할 필요 없이 Spark Job을 실행할 수 있고, Step Functions을 통해 데이터 파이프라인을 자동화할 수 있다. 이를 위해 IAM Role과 iam:PassRole 설정이 필수이며, 상태 머신(JSON)을 활용해 실행할 PySpark 스크립트를 동적으로 변경할 수 있다. 배포를 자동화하기 위해 deploy.sh 스크립트를 사용하면 PySpark 스크립트를 S3에 업로드하고 Step Functions을 업데이트 및 실행할 수 있다. 결과적으로, 반복적인 작업을 자동화하고 운영 부담을 줄일 수 있으며, 확장성과 유지보수성이 향상된다.&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 성능 최적화 및 dynamoDB JSON 관련한 내용을 다뤄보도록 하겠다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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. 참고 : &lt;a href=&quot;https://github.com/aws-samples/emr-serverless-samples&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/aws-samples/emr-serverless-samples&lt;/a&gt;&lt;/p&gt;</description>
      <category>STUDY/Data Engineering</category>
      <author>wonpick</author>
      <guid isPermaLink="true">https://pickwon.tistory.com/192</guid>
      <comments>https://pickwon.tistory.com/192#entry192comment</comments>
      <pubDate>Sun, 2 Feb 2025 23:57:39 +0900</pubDate>
    </item>
    <item>
      <title>Teleport 기반 Core Tunnel 도입으로 로컬 개발 환경 효율성 극대화하기</title>
      <link>https://pickwon.tistory.com/191</link>
      <description>&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;b&gt;0. 개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JupyterHub를 통해 개발을 하고 있고 Teleport의 HTTP Proxy 기능을 활용해 원격 사용자가 웹 브라우저를 통해 안전하게 접속할 수 있도록 설정되어 있습니다.&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;최근 Teleport를 사용한 Proxy 연결 중 알 수 없는 끊어짐이 간헐적으로 발생하고 있어, 세션 안정성을 강화하기 위한 대안으로 SSH 기반의 기술을 통해 WEB JupyterHub를 사용할 수 있는 환경을 구축하기로 하였습니다.&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;그렇게 결론을 짧게 말하자면 SSH 연결을 통해 vs code에서 로컬 개발 환경을 구축하여 간단하게 작업도 가능하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Teleport를 사용하고 있음에도 왜 core tunnel이라는 것을 도입했는지, 도입했을때의 장점에 대해 알아보도록 하겠습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 95.5813%; height: 85px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;Teleport HTTP Application&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;core tunnel - SSH (Secure Shell)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;프로토콜&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;HTTP(S)&lt;/td&gt;
&lt;td&gt;SSH 프로토콜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;연결 안정성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;HTTP는 상태가 없는(stateless) 프로토콜로, 세션 유지에 의존적&lt;/td&gt;
&lt;td&gt;SSH는 상태가 있는(stateful) 연결을 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;끊김 원인&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;네트워크 지연, HTTP 세션 만료, Keep-Alive 설정 불량 등&lt;/td&gt;
&lt;td&gt;주로 네트워크 불안정, 터널 설정 문제 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;목적&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;웹 애플리케이션 또는 HTTP 서비스에 주로 사용&lt;/td&gt;
&lt;td&gt;터미널, 파일 전송, 포트 포워딩 등 광범위한 서버 작업 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;VSCode와의 연동&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;간접적으로 지원 (HTTP를 통한 REST API 통신)&lt;/td&gt;
&lt;td&gt;VSCode의 Remote SSH 확장 프로그램에서 직접적으로 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Core Tunnel이란 무엇인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Core Tunnel은 &lt;b&gt;SSH(Secure Shell)&lt;/b&gt; 기반의 네트워크 터널링 기술로, 원격 서버와 로컬 머신 간에 암호화된 &quot;터널&quot;을 생성하여 데이터를 안전하게 주고받을 수 있는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 외부에서 직접 접근할 수 없는 내부 리소스(예: 원격 데이터베이스, 내부 API 서버)에 안전하게 접근할 수 있습니다.&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;Core Tunnel은 SSH 프로토콜의 &lt;b&gt;포트 포워딩&lt;/b&gt; 기능을 활용하여 특정 포트를 안전한 통로로 연결합니다. 이를 통해 로컬 머신에서 원격 서버의 자원을 마치 로컬 리소스처럼 사용할 수 있게 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.&amp;nbsp; 왜 Core Tunnel이 필요한가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 HTTP 기반의 통신 방식은 다음과 같은 문제를 야기할 수 있습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;끊김 현상:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 기반 연결은 짧은 세션 유지 기간 때문에 데이터 전송이 끊기거나 세션이 종료되는 일이 잦습니다.&lt;/li&gt;
&lt;li&gt;특히 Teleport와 같은 HTTP Proxy 기반 접근에서는 Session Replay와 같은 기능 활성화로 연결 불안정성이 더 증가할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;외부 접근 제한:&lt;/b&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안 취약성:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;암호화되지 않은 연결은 네트워크 상의 데이터 도청이나 중간자 공격(Man-in-the-Middle)에 노출될 위험이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Core Tunnel은 이러한 문제를 해결하기 위한 대안으로 사용됩니다. SSH를 통해 &lt;b&gt;암호화된 안전한 통신 채널&lt;/b&gt;을 제공하며, 끊김 현상을 줄이고 지속 가능한 세션을 유지합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Core Tunnel의 장단점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;보안성:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSH 암호화를 통해 모든 트래픽이 보호되며, 외부 공격에 안전합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;세션 안정성:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Keep-Alive 기능을 통해 세션이 끊기지 않고 지속적으로 유지됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유연성:&lt;/b&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;VPN 대체:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 VPN 설정 없이도 내부 네트워크 리소스에 접근할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;초기 설정 복잡성:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSH 키 또는 인증 설정이 필요하며, 이를 구성하는 데 약간의 학습 곡선이 존재합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;세션 의존성:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSH 세션이 종료되면 터널도 함께 종료되므로, 세션 관리가 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 제한:&lt;/b&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;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 신규 JupyterHub 작업환경에서 Core Tunnel 활용&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;문제점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 Teleport를 통한 HTTP Proxy 기반 연결에서 끊김 현상이 자주 발생.&lt;/li&gt;
&lt;li&gt;특히, Session Replay 기능 활성화로 인해 세션의 지속성이 떨어졌음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대안:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Core Tunnel을 통해 SSH 기반 세션을 제공.&lt;/li&gt;
&lt;li&gt;JupyterHub 등 다양한 도구가 안정적으로 연결될 수 있는 환경 조성.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSH 기반 Core Tunnel은 세션 안정성과 네트워크 보안을 동시에 강화.&lt;/li&gt;
&lt;li&gt;JupyterHub 및 데이터베이스 관리와 같은 작업에 높은 활용성을 제공.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. Core Tunnel vs 다른 SSH 기반 기술&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;기능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;Core Tunnel&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;Autossh&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;Ngrok&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;Teleport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SSH 기반&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;td&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;자동 재연결&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;예&lt;/td&gt;
&lt;td&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;보안&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;SSH 암호화&lt;/td&gt;
&lt;td&gt;SSH 암호화&lt;/td&gt;
&lt;td&gt;SSL 암호화&lt;/td&gt;
&lt;td&gt;SSH 및 고급 보안&lt;/td&gt;
&lt;/tr&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;td&gt;매우 높음&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;/tr&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;td&gt;HTTP/TCP 터널링&lt;/td&gt;
&lt;td&gt;인증 및 감사 로그&lt;/td&gt;
&lt;/tr&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;td&gt;간단한 리소스 노출&lt;/td&gt;
&lt;td&gt;엔터프라이즈 환경&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6.결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Core Tunnel은 안전하고 지속 가능한 연결을 제공하며, 기존 HTTP 기반 세션의 끊김 문제를 효과적으로 해결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, JupyterHub와 같은 개발 환경에서는 Core Tunnel을 활용하여 작업의 연속성을 유지하고 네트워크 보안을 강화할 수 있는 최적의 솔루션으로 자리 잡을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, Core Tunnel은 &lt;b&gt;원격 서버에 연결된 리소스를 로컬 환경에서도 마치 로컬 자원처럼 활용&lt;/b&gt;할 수 있도록 지원합니다. JupyterHub 에서도 기존에 사용하던 커널을 그대로 사용하여 개발이 가능합니다. 이를 통해 개발자는 로컬 머신에서 데이터베이스, JupyterHub, 또는 기타 원격 서비스에 안전하게 접속하며, 로컬 개발 환경에서 작업의 효율성을 높일 수 있습니다.&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;Core Tunnel를 도입했다고 하여 Teleport를 사용하지 않는 것이 아닌 개발 환경은 Teleport를 기반으로 동작하며, Teleport 세션을 통해 원격 서버에 접속한 후 추가적인 SSH 터널링을 설정한 형태로 작업을 진행하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>STUDY</category>
      <author>wonpick</author>
      <guid isPermaLink="true">https://pickwon.tistory.com/191</guid>
      <comments>https://pickwon.tistory.com/191#entry191comment</comments>
      <pubDate>Sun, 5 Jan 2025 23:00:16 +0900</pubDate>
    </item>
    <item>
      <title>Docker 컨테이너에서 볼륨 마운트 파일 권한 문제와 해결 방법</title>
      <link>https://pickwon.tistory.com/190</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Docker를 사용하다 보면, 컨테이너 내부에서 볼륨 마운트된 파일에 접근하려고 할 때 권한 문제가 발생하는 경우가 있습니다. 특히, 기본적으로 설정된 컨테이너 사용자의 UID와 호스트 파일의 소유자가 다를 때 이런 문제가 두드러집니다. 이번 글에서는 이 문제의 원인과 해결 방법을 정리해 보겠습니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;1. 문제 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 컨테이너에서 Jupyter 기반의 PySpark 환경(jupyter/pyspark-notebook)을 설정한 후, 호스트 디렉터리를 컨테이너와 공유하기 위해 다음과 같이 볼륨 마운트를 설정했습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;docker run -d --name dwspark \
  -v /home/dawon/projects/spark_notebook/sparkdata:/home/jovyan/sparkdata \
  -v /home/dawon/projects/spark_notebook/work:/home/jovyan/work \
  -p host port:8888 \
  -p host port:4040 \
  dwspark:0.1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 내부에서는 /home/jovyan 사용자가 파일을 관리하도록 되어 있지만, 볼륨 마운트된 디렉터리의 파일은 &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;컨테이너 내부에서 jovyan 사용자가 볼륨 마운트된 파일을 수정하려고 할 때 &lt;b&gt;&quot;Permission denied&quot;&lt;/b&gt; 오류 발생.&lt;/li&gt;
&lt;li&gt;호스트 파일의 소유자가 UID 2011로 설정되어 있었지만, 컨테이너 내부의 jovyan 사용자는 기본적으로 UID 1000이었음.&lt;/li&gt;
&lt;li&gt;UID 불일치로 인해 컨테이너 내부에서 파일에 대한 접근 권한이 제한됨.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 원인 분석&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) 볼륨 마운트의 특성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker의 볼륨 마운트는 호스트 파일 시스템과 컨테이너 파일 시스템을 연결합니다. 하지만 이 과정에서 &lt;b&gt;파일 소유자 및 권한 정보는 호스트 파일 시스템의 설정을 따릅니다.&lt;/b&gt;&lt;br /&gt;따라서, 컨테이너 내부에서 특정 파일의 소유자 정보를 확인해 보면 다음과 같은 상황이 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트 파일의 소유자가 UID 2011이라면, 컨테이너 내부에서도 UID 2011로 표시됨.&lt;/li&gt;
&lt;li&gt;컨테이너 사용자가 UID 1000이라면, 이 사용자는 해당 파일의 소유자가 아니므로 읽기/쓰기 권한이 제한됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(2) UID 불일치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 내부의 기본 사용자 jovyan은 UID 1000을 사용합니다. 하지만 호스트 파일의 소유자는 UID 2011로 설정되어 있었기 때문에, 컨테이너 내부에서는 파일 소유권이 맞지 않아 권한 문제가 발생했습니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 컨테이너 내부와 호스트 간의 UID를 일치시키거나, 파일 권한을 조정하는 방법이 필요합니다. 아래는 이를 해결하는 세 가지 방법입니다.&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 data-ke-size=&quot;size20&quot;&gt;(1)&amp;nbsp; 컨테이너 사용자의 UID를 호스트와 맞추기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile을 수정하여 컨테이너 내부의 jovyan 사용자의 UID를 호스트와 동일하게 설정합니다.&lt;br /&gt;아래는 이를 구현한 Dockerfile 예제입니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;FROM jupyter/pyspark-notebook:spark-3.3.0

USER root
# 컨테이너 내부 jovyan 사용자의 UID를 2011로 변경
RUN usermod -u 2011 jovyan
# 변경된 UID에 맞게 홈 디렉터리 소유권 수정
RUN chown -R 2011 /home/jovyan
USER jovyan
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Dockerfile을 기반으로 이미지를 빌드한 후 실행하면, 컨테이너 내부의 jovyan 사용자가 호스트 파일의 소유자 UID와 일치하게 되어 권한 문제가 해결됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(2) 호스트 파일의 소유권 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트 파일의 UID를 컨테이너 내부 사용자와 동일하게 변경할 수도 있습니다. 이 방법은 다음 명령어를 사용해 구현합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;sudo chown -R 1000:1000 /home/dawon/projects/spark_notebook/sparkdata
sudo chown -R 1000:1000 /home/dawon/projects/spark_notebook/work
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어는 호스트 파일 소유자를 UID 1000으로 변경하여, 컨테이너 내부의 기본 사용자 jovyan과 일치시킵니다. 그러나 이 방법은 호스트의 다른 사용자가 파일에 접근할 경우 문제를 일으킬 수 있으므로 주의가 필요합니다.&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;(3) 파일 권한 완화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 환경에서만 사용할 수 있는 간단한 방법으로, 호스트 파일의 권한을 모든 사용자에게 열어주는 것입니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;chmod -R 777 /home/dawon/projects/spark_notebook/sparkdata
chmod -R 777 /home/dawon/projects/spark_notebook/work
&lt;/code&gt;&lt;/pre&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 정리 및 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너에서 볼륨 마운트를 사용할 때 권한 문제를 해결하려면, &lt;b&gt;호스트와 컨테이너 간의 UID를 일치시키는 것이 가장 안전하고 권장되는 방법&lt;/b&gt;입니다. Docker 컨테이너에서 볼륨 마운트를 효과적으로 사용하려면, UID와 권한 설정을 정확히 이해하고 관리해야 합니다. 이 글이 비슷한 문제를 겪는 분들에게 도움이 되길 바랍니다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발</category>
      <author>wonpick</author>
      <guid isPermaLink="true">https://pickwon.tistory.com/190</guid>
      <comments>https://pickwon.tistory.com/190#entry190comment</comments>
      <pubDate>Tue, 10 Dec 2024 13:18:05 +0900</pubDate>
    </item>
  </channel>
</rss>