Communitygithub.com

Spark & PySpark 性能调优

编写并调优 PySpark 作业——涵盖连接策略与广播大小限制、shuffle 分区大小、数据倾斜诊断与加盐处理、UDF 规避、缓存策略及输出文件布局,并提供具体的阈值和量化指标。

Spark & PySpark 性能调优 是什么?

该技能专注于 PySpark 作业的编写与性能调优,适用于 Spark 作业速度缓慢、需要决定是否广播连接、出现长尾任务或作业在连接阶段发生 OOM 等场景。它提供了一套系统化的操作流程:从收集作业代码与 Spark UI 信息开始,逐步指导如何设置合理的会话配置(如启用 AQE 并调整 shuffle 分区数)、优先使用 DataFrame API 和向量化 UDF 避免 Python 序列化开销、根据数据量选择正确的连接策略(广播连接 vs sort-merge 连接)、诊断并修复数据倾斜(通过 AQE 或加盐法)、控制分区与输出文件大小,以及仅在必要时缓存数据。该技能支持 PySpark(DataFrame API),并强调通过 `df.explain(True)` 验证执行计划。核心原则是:在调大集群规模之前,必须先通过分析执行计划找到根本原因。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/SkillMedev/skills/tree/HEAD/skills/spark-jobs

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Spark & PySpark

几乎每一个慢速 Spark 作业都逃不过三个原因之一:一次本可避免的 shuffle、一个倾斜的键、或者一行行通过 Python 慢速处理的数据。最大的错误是在阅读执行计划之前就调整集群规模——花钱买更多的 executor 来跑同一个低效的 DAG。

操作流程

步骤 1:收集输入

  1. 作业代码,对于慢速作业,还需要 Spark UI 中慢速阶段的视图(任务持续时间分布、shuffle 读写大小)以及 df.explain(True) 的输出。
  2. 输入数据的大小和格式,以及每个连接两侧数据的大小。
  3. 集群配置:executor 数量、核心数、内存。
  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);它会在运行时合并 shuffle 分区并切换连接策略。然后仔细设置 spark.sql.shuffle.partitions:目标为每个 shuffle 分区 100-200 MB,因此分区数 ≈ shuffle 数据大小 ÷ 128 MB,并且至少是总 executor 核心数的 2-3 倍,以确保没有核心空闲。

步骤 3:坚持使用 DataFrame API,远离 Python

DataFrame 操作会经过 Catalyst 和 Tungsten,从而获得查询优化和堆外内存。仅当没有 DataFrame 原语可用时才降级到 RDD。Python UDF 会将每一行序列化到 Python 工作进程再返回,破坏了 codegen。优先级顺序:

  1. 使用内置的 pyspark.sql.functions(例如 F.regexp_extract, F.when)。
  2. 当无法避免 Python 时,使用 Pandas(向量化)UDF。
  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(默认 10 MB)的表;当 executor 内存允许时,可以显式广播大小达到几百 MB 的表,但大约 1 GB 是实际天花板——广播会将表复制到每个 executor,如果判断失误会导致作业 OOM。
from pyspark.sql.functions import broadcast
result = large.join(broadcast(small), "key")
  • 在连接之前过滤列和行,以减少 shuffle 数据量;每减少一列和一行就节省了 shuffle 的字节数。
  • 两个大表 → 接受 sort-merge 连接,但先检查是否存在数据倾斜(步骤 5)。

步骤 5:诊断并修复数据倾斜

数据倾斜的症状:阶段中只剩下一两个任务未完成;最大任务持续时间超过中位数的约 3-5 倍;Spark UI 中某个 shuffle 读取分区远大于其他分区。AQE 的倾斜连接处理会在分区大小超过中位数大小的 5 倍且超过 256 MB(默认值)时拆分该分区——在执行计划中确认它是否已触发。当 AQE 无法帮助时(例如倾斜聚合),对热点键进行加盐处理:对大表一侧的键追加随机后缀 0..N,将小表一侧在所有 N 个后缀上展开,连接,然后去除盐。选择 N ≈ 倾斜因子(如果某个键是中位数的 20 倍,则 N=20)。

步骤 6:控制分区和输出布局

  • repartition(n, col) 会进行全 shuffle 以平衡数据;在宽输出之前使用。
  • coalesce(n) 减少分区但不进行 shuffle;用于避免产生过小的输出文件。
  • 目标输出文件大小为 128 MB - 1 GB;成千上万的小文件会拖慢每个下游读取器。
  • 仅按低基数列(日期、区域——几十到几百个值,绝不使用 ID)进行分区输出:
df.write.partitionBy("dt").mode("overwrite").parquet("/data/out")

步骤 7:仅缓存被重用的数据

仅当 DataFrame 在 DAG 中被多次使用时才缓存,将其物化,并在完成后取消缓存:

df.cache()
df.count()  # 物化
# ... 重用 df ...
df.unpersist()

缓存一次使用的 DataFrame 会浪费内存,并可能驱逐真正重要的数据。

步骤 8:通过执行计划验证

重新运行 df.explain(True),确认预期的策略已经出现(BroadcastHashJoin、下推的过滤器、没有出现本应被消除的 Exchange)。读取列式格式(Parquet、ORC)以便谓词下推能够工作,永远不要对大量数据使用 collect() 将其拉取到 driver,并且在链式调用中优先使用 F.col 引用而不是字符串列名。

示例:糟糕的连接 vs 好的连接

糟糕的——一个 2 TB 的事件表连接一个 200 MB 的维度表,全列宽度,使用 Python UDF 进行解析:

result = (events.join(dims, "store_id")          # sort-merge:shuffle 了全部 2 TB
    .withColumn("region", parse_region_udf("meta"))  # 逐行的 Python 处理
    .filter(F.col("dt") == "2024-06-01"))            # 连接之后才过滤

执行计划显示两侧都有 Exchange(shuffle 了 2 TB),并且 UDF 阻止了 codegen。

好的——先过滤和投影,广播维度表,使用内置函数:

result = (events
    .filter(F.col("dt") == "2024-06-01")              # 首先裁剪到约 30 GB
    .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)))

现在执行计划显示 BroadcastHashJoin,没有大表的 Exchange:扫描约 30 GB,大表零 shuffle,没有 Python 边界。

交付物

生成修订后的作业代码以及一份调优说明,内容应包括:每个连接的策略及其大小依据、shuffle 分区设置及其计算过程、发现的任何数据倾斜(最大与中位数任务比值)及修复措施、缓存决策、输出文件大小预期,以及(对于调优)调优前后的 explain 差异。

禁止事项

  • 不要广播一个你没有估算大小的表——一个错误的广播会瞬间导致所有 executor OOM。
  • 在检查 pyspark.sql.functions 之前不要编写 Python UDF;几乎所有字符串、日期和条件逻辑都有内置函数。
  • 不要对一个大型 DataFrame 使用 collect() 或 toPandas();它会把集群的所有数据都灌入一个 driver。
  • 不要按高基数列对输出进行分区——会产生数百万个目录,每个目录只有一行。
  • 不要因为一个倾斜的任务导致慢速阶段就增加 executor;更多的核心无法拆分一个分区。
  • 不要出于习惯任意缓存;只缓存那些在后续步骤中被重用的 DataFrame,并在使用后取消缓存。

质量标准

一个完成的作业只有在满足以下条件时才算是合格的:每个连接都标明了其策略以及该策略的大小依据;spark.sql.shuffle.partitions 是根据数据大小计算得出的,而不是沿用习惯值;Spark UI 中 shuffle 阶段的任何任务持续时间不超过中位数持续时间的约 3 倍(或者已解释并修复了倾斜);任何可用的内置函数替代了标量 Python UDF;输出文件大小落在 128 MB - 1 GB 范围内;并且最终的 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.

相关技能