Communitygithub.com

Spark & PySpark

PySpark 작업을 작성하고 튜닝합니다 – 조인 전략과 브로드캐스트 크기 제한, 셔플 파티션 크기, 데이터 쏠림 진단 및 솔팅(salting), UDF 회피, 캐싱, 출력 파일 배치를 구체적인 크기와 쏠림 임계값과 함께 다룹니다.

Spark & PySpark란 무엇인가요?

이 스킬은 Apache Spark와 PySpark 작업을 분석하고 최적화하는 AI 에이전트를 위한 전문 지식입니다. 느린 Spark 작업의 일반적인 원인인 불필요한 셔플, 데이터 쏠림(skew), Python UDF로 인한 비효율을 진단하고 수정하는 방법을 제공합니다. 지원하는 주요 플랫폼은 PySpark 기반의 Spark 환경이며, 클러스터 크기를 늘리기 전에 실행 계획을 분석하여 근본 원인을 해결하는 접근법을 강조합니다. 이 스킬은 작업 코드와 Spark UI 정보를 입력으로 받아 조인 전략 선택(브로드캐스트 vs 소트-머지), 셔플 파티션 크기 산정, 데이터 쏠림 진단 및 솔팅(salting) 처리, UDF 회피를 위한 내장 함수 사용, 캐싱 전략, 출력 파일 크기 최적화 등을 단계별로 안내하며, 최종적으로 튜닝 노트와 개선된 코드를 산출합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/SkillMedev/skills/tree/HEAD/skills/spark-jobs

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Spark & PySpark은(는) 무엇을 하나요?

느린 Spark 작업의 거의 대부분은 세 가지 이유 중 하나로 느립니다: 피할 수 있는 셔플(avoidable shuffle), 쏠린 키(skewed key), 또는 한 번에 한 행씩 Python을 통해 데이터가 누출되는 경우입니다. 비용이 많이 드는 실수는 계획을 읽기 전에 클러스터 크기를 조정하는 것입니다 – 더 많은 익스큐터(executor) 비용을 지불하여 동일한 비효율적인 DAG를 더 빠르게 실행하는 것입니다.

운영 절차

1단계: 입력 수집

  1. 작업 코드와, 느린 작업의 경우 느린 스테이지의 Spark UI 보기(작업 기간 분포, 셔플 읽기/쓰기 크기) 및 df.explain(True) 출력.
  2. 입력 데이터 크기와 형식, 그리고 모든 조인의 양쪽 크기.
  3. 클러스터 형태: 익스큐터 수, 코어 수, 메모리.
  4. 작업이 새로운 작성인지 튜닝 패스인지 – 튜닝의 경우 코드를 건드리기 전에 계획에서 진단합니다.

2단계: 올바른 세션에서 시작

from pyspark.sql import SparkSession
from pyspark.sql import functions as F

spark = (
    SparkSession.builder
    .appName("etl")
    .config("spark.sql.shuffle.partitions", "200")
    .config("spark.sql.adaptive.enabled", "true")
    .getOrCreate()
)

적응형 쿼리 실행(AQE)을 활성화합니다. AQE는 런타임에 셔플 파티션을 병합하고 조인 전략을 전환합니다. 그런 다음 spark.sql.shuffle.partitions를 의도적으로 설정합니다: 셔플 파티션당 100-200MB를 목표로 하므로, partitions ≈ 셔플 데이터 크기 ÷ 128MB이고, 총 익스큐터 코어의 최소 2-3배가 되어 코어가 유휴 상태가 되지 않도록 합니다.

3단계: DataFrame API 안에 머물고 Python을 벗어나기

DataFrame 연산은 Catalyst와 Tungsten을 통해 쿼리 최적화와 off-heap 메모리를 제공합니다. RDD는 DataFrame 기본 요소가 없는 경우에만 사용합니다. Python UDF는 각 행을 Python 워커로 직렬화하고 다시 되돌려 코드 생성을 차단합니다. 선호 순서:

  1. 내장 pyspark.sql.functions (예: F.regexp_extract, F.when).
  2. Pandas (벡터화) UDF – Python이 불가피할 때 사용.
  3. 스칼라 Python UDF – 최후의 수단으로만 사용.
from pyspark.sql.functions import pandas_udf

@pandas_udf("double")
def normalize(s):
    return (s - s.mean()) / s.std()

4단계: 크기에 따라 조인 전략 선택

  • 작은 쪽이 메모리에 들어갈 때는 브로드캐스트합니다. Spark는 spark.sql.autoBroadcastJoinThreshold(기본값 10MB) 이하를 자동으로 브로드캐스트합니다. 익스큐터 메모리가 허용할 때 수백 MB까지의 테이블은 명시적으로 브로드캐스트하며, ~1GB를 실질적인 상한선으로 간주합니다 – 브로드캐스트는 테이블을 모든 익스큐터에 복사하므로 잘못 판단하면 작업이 OOM됩니다.
from pyspark.sql.functions import broadcast
result = large.join(broadcast(small), "key")
  • 조인 전에 열을 필터링하고 프로젝션하여 셔플을 줄입니다. 모든 드롭된 열과 행은 절약된 셔플 바이트입니다.
  • 두 개의 큰 테이블 → 소트-머지 조인을 수용하지만, 먼저 쏠림을 확인합니다(5단계).

5단계: 쏠림 진단 및 수정

쏠림 증상: 하나나 두 개의 작업을 제외한 스테이지가 완료됨; 최대 작업 기간이 중앙값의 ~3-5배를 초과함; Spark UI에서 하나의 셔플 읽기 파티션이 나머지보다 훨씬 큼. AQE의 쏠림 조인 처리(skew-join handling)는 파티션이 중앙값 파티션 크기의 5배를 초과하고 256MB(기본값)를 초과할 때 분할합니다 – 계획에서 실행되었는지 확인합니다. AQE가 도움이 되지 않을 때(예: 쏠린 집계), 핫 키(hot key)에 소금(salt)을 뿌립니다: 큰 쪽의 키에 0..N 범위의 임의 접미사를 추가하고, 작은 쪽을 모든 N개의 접미사로 확장한 후 조인한 다음 소금을 제거합니다. N을 쏠림 계수에 가깝게 선택합니다(중앙값의 20배인 키는 N=20).

6단계: 파티셔닝 및 출력 레이아웃 제어

  • repartition(n, col)은 데이터를 균형 있게 맞추기 위해 전체 셔플을 수행합니다. 넓은 쓰기 전에 사용합니다.
  • coalesce(n)은 셔플 없이 파티션 수를 줄입니다. 아주 작은 출력 파일을 피하기 위해 사용합니다.
  • 출력 파일의 목표 크기는 128MB-1GB입니다. 수천 개의 작은 파일은 모든 다운스트림 리더에 부담을 줍니다.
  • 출력은 카디널리티가 낮은 열(날짜, 지역 – 수십에서 수백 개의 값, ID는 절대 안 됨)로만 파티션합니다:
df.write.partitionBy("dt").mode("overwrite").parquet("/data/out")

7단계: 재사용되는 것만 캐시

DataFrame이 DAG에서 여러 번 사용될 때만 캐시하고, 구체화한 후 사용이 끝나면 unpersist합니다:

df.cache()
df.count()  # materialize
# ... reuse df ...
df.unpersist()

한 번만 사용되는 DataFrame을 캐시하면 메모리를 낭비하고 중요한 데이터를 축출(evict)할 수 있습니다.

8단계: 계획으로 확인

df.explain(True)를 다시 실행하여 의도한 전략이 나타나는지 확인합니다(BroadcastHashJoin, 푸시된 필터, 제거된 Exchange가 없는지). 컬럼 형식(Parquet, ORC)을 읽어 조건자 푸시다운(predicate pushdown)이 작동하도록 하고, 큰 데이터에 대해 collect()를 절대 사용하지 말며, 체이닝 시 문자열 열보다 F.col 참조를 선호합니다.

작업 예제: 나쁜 조인 vs 좋은 조인

나쁜 예 – 2TB 이벤트 테이블이 200MB 차원 테이블에 조인되고, 전체 너비, Python UDF로 파싱:

result = (events.join(dims, "store_id")          # sort-merge: 2TB 전체 셔플
    .withColumn("region", parse_region_udf("meta"))  # 행 단위 Python
    .filter(F.col("dt") == "2024-06-01"))            # 조인 후 필터

계획은 양쪽에 Exchange를 보여주고(2TB 셔플됨), UDF가 코드 생성을 차단합니다.

좋은 예 – 먼저 필터링하고 프로젝션, 차원을 브로드캐스트, 내장 함수 사용:

result = (events
    .filter(F.col("dt") == "2024-06-01")              # 먼저 ~30GB로 축소
    .select("store_id", "amount", "meta")
    .join(broadcast(dims.select("store_id", "region_code")), "store_id")
    .withColumn("region", F.regexp_extract("meta", r"region=(\w+)", 1)))

계획은 이제 큰 쪽의 Exchange 없이 BroadcastHashJoin을 보여줍니다: ~30GB 스캔, 큰 테이블의 셔플 제로, Python 경계 없음.

산출물

수정된 작업 코드와 함께 튜닝 노트를 작성합니다: 각 조인에 대한 조인 전략과 크기 증거, 셔플-파티션 설정과 그 계산, 발견된 모든 쏠림(최대 대 중앙값 작업 비율)과 적용된 수정, 캐싱 결정, 출력 파일 크기 예상, 그리고 튜닝 패스의 경우 이전/이후 explain 차이.

금지 사항

  • 크기를 확인하지 않은 테이블을 브로드캐스트하지 마십시오 – 잘못 판단된 브로드캐스트는 모든 익스큐터를 동시에 OOM시킵니다.
  • pyspark.sql.functions을 확인하기 전에 Python UDF를 작성하지 마십시오; 거의 모든 문자열, 날짜 및 조건부 로직에 내장 함수가 있습니다.
  • 큰 DataFrame에 대해 collect() 또는 toPandas()를 사용하지 마십시오 – 클러스터의 데이터를 하나의 드라이버로 모읍니다.
  • 출력을 카디널리티가 높은 열로 파티션하지 마십시오 – 수백만 개의 디렉토리, 각각 한 행씩.
  • 느린 스테이지가 하나의 쏠린 작업인 작업에 익스큐터를 추가하지 마십시오 – 더 많은 코어로 하나의 파티션을 나눌 수 없습니다.
  • 반사적으로 캐시하지 마십시오 – 다운스트림에서 재사용되는 DataFrame만 캐시하고, unpersist하십시오.

품질 기준

완료된 작업은 다음 조건을 모두 충족해야 합니다: 모든 조인이 그 전략과 크기 증거를 명시함; spark.sql.shuffle.partitions가 데이터 크기에서 파생되고 기본값으로 남지 않음; Spark UI가 셔플 스테이지에서 중앙값 기간의 ~3배 이상인 작업을 보여주지 않음(또는 쏠림이 설명되고 수정됨); 내장 함수가 존재하는 곳에 스칼라 Python UDF가 남아 있지 않음; 출력 파일이 128MB-1GB 범위에 속함; 최종 explain이 의도한 계획을 확인함.

Individual skills in this repo

This repo contains 19 individual skills — each has its own dedicated page.

SkillMedev/skills

Designs REST API surfaces - resource naming, HTTP method and status-code semantics, error shapes, pagination, and filtering - and delivers an endpoint spec a consumer can build against without asking questions. Use when someone asks "how should I name this endpoint", "what status code should this return", "should this be PUT or PATCH", "how do I paginate this list", or is reviewing an API before it ships to external consumers. Do NOT use for planning breaking-change rollouts and deprecation windows - use api-versioning-strategist instead; for GraphQL type and resolver design - use graphql-schema instead; for generating client SDKs from an existing spec - use api-client-generator instead; for designing inbound webhook endpoints - use webhook-receiver-hardener instead.

SkillMedev/skills

Produce an accurate, properly timed caption track (SRT or WebVTT) from a video's audio - transcribing or aligning to the voiceover script, timing cues to speech, and enforcing line-length and reading-speed rules so captions are readable and in sync. Use when someone says "generate captions", "make subtitles from the audio", "transcribe and caption this", "export an SRT or VTT", "the captions are out of sync", or "the subtitles flash by too fast to read". Do NOT use to burn-in, style, reframe, or position an existing caption track for a platform (9:16, brand styling, safe areas) - that is social-video-formatter; do NOT use to animate text word-by-word as a motion graphic - that is kinetic-typography; do NOT use to write the spoken script in the first place - that is narration-script.

SkillMedev/skills

Turns data and charts into a decision-driving narrative structured as headline finding, trend, implication, and recommended action - with finding-led chart titles, context for every number, annotation guidance, and honest flags on any conclusion the data cannot support. Use when someone says "turn these numbers into a story", "what's the takeaway from this data", "help me present these results to leadership", or has charts but no narrative. Do NOT use for compressing a long document into a one-pager - use executive-summary instead - or for running the analysis that produces the findings - use eda-playbook instead.

SkillMedev/skills

Use when a task needs live or historical money data - "convert USD to EUR", "current/past exchange rate", "FX rate on this date / over this range", or "current price of Bitcoin/Ethereum, market cap, 24h change". Frankfurter (ECB reference rates, no key) is the FX default; CoinGecko's free keyless tier covers crypto. Do NOT use for stock quotes or equities - no keyless stock API survives verification, say so instead of guessing; do NOT use for country economic indicators like GDP or inflation series - use government-open-data instead; if the request is a vague "I need live data", route through public-data-api-picker.

SkillMedev/skills

Builds a driver-based FP&A operating model linking business inputs to P&L, balance sheet, and cash flow outputs. Use when building an annual plan, preparing investor materials, running scenario analysis, or stress-testing the business.

SkillMedev/skills

Use when a task needs live geographic lookups - "geocode this address", "what's at these coordinates" (reverse geocoding), "lat/lon for this city", "which country/state is this ZIP or postal code in", or "country facts: capital, currency, population, flag". Nominatim (OpenStreetMap) is the geocoding default; Zippopotam for postal codes; APICountries for country facts. All keyless. Do NOT use for weather at a location - use weather-climate instead; do NOT use for country-level statistics over time (GDP, population trends) - use government-open-data instead; if the request is a vague "I need live data", route through public-data-api-picker.

SkillMedev/skills

Runs the full Getting Things Done loop - capture, clarify, organize, reflect, engage - building a trusted system of context lists, a projects list with defined next actions, and a weekly review habit. Use when someone says "I'm overwhelmed and things are slipping through the cracks", "set up GTD for me", "help me do a brain dump and organize it", or "my to-do list is a mess". Do NOT use for just running the weekly review ritual itself - use weekly-review instead - or for clearing an email backlog - use inbox-zero.

SkillMedev/skills

Processes any email backlog to zero using the 4Ds - Delete, Delegate, Defer, Do - with a mass-archive strategy for the obvious, a touch-each-email-once discipline, and a keep-it-clear system of batched processing windows, ruthless unsubscribing, filters, and a minimal folder setup. Use when someone says "I have 5,000 unread emails", "help me get to inbox zero", "email is eating my whole day", or treats their inbox as a to-do list. Do NOT use for drafting the reply emails themselves or prioritization rules for an ongoing support queue - use email-triage instead - or for protecting focus time around the email windows - use deep-work-planner instead.

SkillMedev/skills

Write landing-page copy section by section - hero, social proof, features-as-benefits, objections, CTA - leading with one promise and proving it down the page. Use when someone asks "write my landing page", "improve my hero headline", "what should the homepage say", "turn these features into benefits", or is shipping a new page for a launch or campaign. Do NOT use for testing and optimizing an existing page's conversion rate - use landing-page-cro instead; for the one-line claim itself use positioning-statement; for the full message ladder use messaging-hierarchy.

SkillMedev/skills

Improves an existing landing page's conversion rate through evidence-first CRO - audit heatmaps and recordings, write structured hypotheses, prioritize with ICE scoring, and run one-variable tests to statistical significance. Use when someone asks "why isn't my landing page converting", "audit this page before we scale spend", "which A/B test should we run first", or after a CVR drop or ahead of a paid-traffic push. Do NOT use for writing a new landing page from scratch - use landing-page-copy instead. Do NOT use for auditing the ad accounts sending the traffic - use paid-acquisition-audit instead. Do NOT use for reading the results of a finished experiment - use ab-test-analyzer instead.

SkillMedev/skills

Runs structured coaching sessions using values clarification and the GROW model, ending every session with one committed action, a deadline, and an if-then plan for the likely obstacle. Use when someone says "I feel stuck in my life", "help me figure out what I want", "hold me accountable to my goals", or "coach me through this decision". Do NOT use for building a stress toolkit - use stress-management instead - or a journaling practice - use journal-framework; for a standing goal-tracking system, use goals-accountability. Coaching, not therapy: signs of clinical distress route to a licensed professional.

SkillMedev/skills

Adapts English content for a target language and region - swapping idioms, setting the right formality register, replacing cultural references, converting dates, units, and currency - and delivers the localized text with a notes column explaining each adaptation and flagging what needs a native reviewer. Use when someone asks "localize this copy for the Japanese market", "adapt our marketing copy for the Mexican market", "make this translation sound native instead of literal", or a literal translation reads foreign, stiff, or risky. Do NOT use for auditing in-product UI text quality - use ux-writing-audit instead.

SkillMedev/skills

Turns a natural-language video brief into a complete, ready-to-preview Remotion composition - extracts duration, scenes, brand colors, aspect ratio, and real copy; plans the frame budget; and writes data-driven React/TypeScript using useCurrentFrame, interpolate, spring, AbsoluteFill, and Sequence, registered in Root.tsx. Use when someone says "build a 20-second product demo video", "animate a feature walkthrough", "write the Remotion code for this marketing clip", or wants a scene-by-scene composition they can scrub in Studio. Do NOT use for rendering, iterating, or batching the finished MP4 - use remotion-render instead - or for installing and scaffolding the project - use remotion-setup instead.

SkillMedev/skills

Scaffolds a new Remotion video project wired for Claude Code Agent Skills - Node check, create-video scaffold, skills install, folder conventions, Google Fonts, and a smoke-test render. Use whenever someone wants to start making videos with Remotion and Claude, even just "make product videos with Claude.

SkillMedev/skills

Classifies incident severity (SEV1-4) using impact, scope, and urgency signals and decides who to page. Use when an alert fires or a report comes in and a severity call must be made quickly.

SkillMedev/skills

Use the Skill Me catalog from inside any conversation - discover, install, and manage Claude skills through the Skill Me MCP, and load installed skills automatically each session.

SkillMedev/skills

Write one platform-native caption from a topic and brand voice, with a front-loaded hook, native length, and a single earned CTA. Use when the user gives a topic (and optionally brand voice or a described image/video) and asks for an Instagram, LinkedIn, X/Twitter, or TikTok caption. Do NOT use when the user wants a dedicated standalone LinkedIn post - use linkedin-post-writer instead; or a multi-post X thread - use tweet-thread-builder instead; or to adapt one existing piece of content into versions for several channels - use cross-platform-reformatter instead.

SkillMedev/skills

Reframe and export a finished video for social platforms - 9:16 ↔ 16:9 cropping, burned-in captions, a platform-native first frame, loop design, and per-platform specs (aspect, length, safe areas) for TikTok, Reels, Shorts, X, and LinkedIn. Use when someone says "make this vertical", "crop to 9:16", "format for TikTok/Reels/Shorts", "add captions/subtitles", "why is my video cut off on mobile", "resize for Instagram", or "export for social". Do NOT use when the question is about the storyboard or shot order before any video exists (use video-storyboard); the animation craft of how elements move (use motion-design-principles); animated word-by-word caption styling (use kinetic-typography); palette, contrast, or lighting (use motion-color-and-light); directing the product-demo content and screen choreography (use product-demo-director); or scoring, beat-syncing, or audio (use sound-and-music-sync) - this is the craft layer that decides WHAT to format for the feed, then hands a concrete spec to remotion-compo...

SkillMedev/skills

Builds clean, performant, accessible SwiftUI views with correct state ownership, scoped invalidation, and smooth list scrolling, and reviews existing SwiftUI code against a concrete frame-time and re-render budget. Use when someone asks "why does my SwiftUI list stutter", "should this be @State or @Observable", "my whole screen re-renders when one row changes", "how do I animate this transition", or wants a SwiftUI view built or refactored. Do NOT use for cross-platform React Native apps - use react-native-pro instead; do NOT use for Flutter widget trees - use flutter-widget-architect instead; do NOT use for Android Compose UIs - use jetpack-compose-builder instead.

관련 스킬