agentsclimarketplace

Pyspark and apache spark analytics

Skill hamzabellouch/agent-skills/Data Engineering and Pipelines/pyspark-and-apache-spark-analytics

Master production PySpark and Apache Spark analytics, distributed data pipelines, Delta Lake/Iceberg optimization, memory management, and skew resolution.From its SKILL.md

Install
npx -y skills add hamzabellouch/agent-skills --skill pyspark-and-apache-spark-analytics

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.6 KB, ~2.0k tokens by cl100k_base, as published. Nobody here has run it

PySpark & Apache Spark Analytics

Comprehensive engineering guide for architecting, optimizing, and operating large-scale distributed data processing pipelines using PySpark and Apache Spark 3.x/4.x.


1. Core Principles & Architecture

1.1 Execution Model

  • Driver: Plans execution, constructs DAGs, manages SparkSession, coordinates tasks, and collects metrics.
  • Executors: Worker processes executing task threads, maintaining in-memory storage (RAM), and handling spill to disk.
  • Transformations vs Actions:
    • Narrow Transformations: Operations where each input partition maps to at most one output partition (map, filter, flatmap). No network shuffle.
    • Wide Transformations: Operations requiring data exchange across cluster partitions (groupByKey, reduceByKey, join, distinct, repartition). Triggers a Shuffle Stage.
    • Actions: Trigger execution of physical execution plan (count, collect, write, first, take).

1.2 Data Lake storage Formats: Delta Lake & Apache Iceberg

  • Acid Transactions: Transaction log (_delta_log or Iceberg metadata trees) providing ACID semantics, time travel, and rollback.
  • Medallion Architecture:
    • Bronze (Raw): Append-only ingestion layer preserving original schema (JSON, CSV, Parquet).
    • Silver (Cleaned/Enriched): Deduplicated, typed, normalized, and joined domain entities.
    • Gold (Aggregated/Business): Star-schema dimensions/facts, aggregated metrics, wide tables optimized for BI/ML queries.

2. Idempotency & Partitioning Strategies

2.1 Dynamic Partition Overwrite & Upserts

Idempotence guarantees that re-running a pipeline execution produces identical state without duplicate records or partial writes.

PySpark Dynamic Partition Overwrite

from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName("IdempotentPartitionWriter") \
    .config("spark.sql.sources.partitionOverwriteMode", "dynamic") \
    .getOrCreate()

# Ensures only affected partitions are replaced upon re-run
df_processed.write \
    .format("parquet") \
    .mode("overwrite") \
    .partitionBy("event_date") \
    .save("s3a://data-warehouse/silver/user_events/")

Delta Lake MERGE (Upsert) Pattern

from delta.tables import DeltaTable

def upsert_to_delta(micro_batch_df, batch_id):
    delta_table = DeltaTable.forPath(spark, "s3a://data-warehouse/silver/user_profiles")
    
    delta_table.alias("target").merge(
        micro_batch_df.alias("updates"),
        "target.user_id = updates.user_id"
    ).whenMatchedUpdate(set={
        "email": "updates.email",
        "updated_at": "updates.updated_at",
        "status": "updates.status"
    }).whenNotMatchedInsert(values={
        "user_id": "updates.user_id",
        "email": "updates.email",
        "created_at": "updates.created_at",
        "updated_at": "updates.updated_at",
        "status": "updates.status"
    }).execute()

3. Performance Optimization & Tuning

3.1 Skew Mitigation Techniques

Data skew occurs when one partition receives disproportionately more data, stalling stage completion (99% task completion syndrome).

Salting Technique for Skewed Joins

from pyspark.sql import functions as F

SALT_FACTOR = 8

# Add random salt to skewed left table
df_skewed = df_large.withColumn("salt", (F.rand() * SALT_FACTOR).cast("integer"))

# Explode right table to match all salt keys
df_small_salted = df_small.withColumn("salt_array", F.array([F.lit(i) for i in range(SALT_FACTOR)])) \
    .withColumn("salt", F.explode("salt_array")) \
    .drop("salt_array")

# Perform join on join_key AND salt key
df_joined = df_skewed.join(
    df_small_salted,
    on=["join_key", "salt"],
    how="inner"
).drop("salt")

3.2 Adaptive Query Execution (AQE)

Enable AQE to dynamically re-optimize execution plans based on runtime statistics:

spark.conf.set("spark.sql.adaptive.enabled", "true")
spark.conf.set("spark.sql.adaptive.coalescePartitions.enabled", "true")
spark.conf.set("spark.sql.adaptive.skewJoin.enabled", "true")
spark.conf.set("spark.sql.adaptive.skewJoin.skewedPartitionFactor", "5")
spark.conf.set("spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes", "256MB")

3.3 Broadcast Join Tuning

Avoid network shuffles for small lookup tables (<10MB default, up to 1GB with tuning):

from pyspark.sql.functions import broadcast

# Explicitly hint Spark to broadcast small dimension
df_fact.join(broadcast(df_dim_small), "dim_id", "inner")

4. Production Anti-Patterns

Anti-PatternOperational RiskProduction Remedy
Calling .collect() on large DataFramesDriver Out-Of-Memory (OOM) crashUse .take(n), write directly to storage, or sample data
Using DataFrame.toPandas() in distributed loopSerialization bottleneck & Driver OOMUse PySpark Pandas API or PyArrow UDFs (pandas_udf)
Unpartitioned .repartition(1) before writeSingle executor bottleneck & disk spillUse .coalesce(n) or dynamic partition overwrites
Row-by-row .iterrows() in Python UDFsContext switching overhead (JVM to Python)Native Spark SQL functions or vectorized pandas_udf
Over-partitioning (tiny files problem)NameNode / S3 metadata strain, slow listingTarget ~128MB - 512MB file sizes, run OPTIMIZE / VORDER

5. Production Implementation Blueprint

Scalable Pipeline with Vectorized UDFs & Structural Monitoring

import sys
import logging
from typing import Iterator
import pandas as pd
from pyspark.sql import SparkSession, DataFrame
from pyspark.sql import functions as F
from pyspark.sql.types import DoubleType, StructType, StructField, StringType, TimestampType

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("SparkAnalyticsEngine")

def build_spark_session(app_name: str) -> SparkSession:
    """Configures high-throughput Spark session optimized for S3/Delta processing."""
    return SparkSession.builder \
        .appName(app_name) \
        .config("spark.sql.adaptive.enabled", "true") \
        .config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
        .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer") \
        .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
        .config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog") \
        .getOrCreate()

# Vectorized Pandas UDF for scalable ML/Analytical scoring
@F.pandas_udf(DoubleType())
def calculate_risk_score_udf(amount_series: pd.Series, velocity_series: pd.Series) -> pd.Series:
    """Vectorized calculation executed in Arrow batches across worker nodes."""
    return (amount_series * 0.45) + (velocity_series * 1.85)

def process_transaction_bronze_to_silver(
    spark: SparkSession,
    bronze_path: str,
    silver_path: str,
    partition_date: str
) -> None:
    """Idempotent Bronze-to-Silver transformation pipeline."""
    logger.info(f"Ingesting Bronze payload from {bronze_path} for date: {partition_date}")

    raw_df = spark.read.format("delta").load(bronze_path) \
        .filter(F.col("event_date") == partition_date)

    # Data Quality Cleanse & Deduplication
    cleaned_df = raw_df \
        .filter(F.col("transaction_id").isNotNull()) \
        .dropDuplicates(["transaction_id"]) \
        .withColumn("amount", F.col("amount").cast(DoubleType())) \
        .withColumn("velocity", F.col("velocity").cast(DoubleType())) \
        .withColumn("risk_score", calculate_risk_score_udf(F.col("amount"), F.col("velocity"))) \
        .withColumn("processed_at", F.current_timestamp())

    logger.info("Writing clean records to Silver Delta table with partition isolation...")
    
    cleaned_df.write \
        .format("delta") \
        .mode("overwrite") \
        .option("replaceWhere", f"event_date = '{partition_date}'") \
        .save(silver_path)

    logger.info(f"Successfully processed Silver partition: {partition_date}")

if __name__ == "__main__":
    spark = build_spark_session("ProductionSparkPipeline")
    try:
        process_transaction_bronze_to_silver(
            spark=spark,
            bronze_path="s3a://enterprise-datalake/bronze/transactions",
            silver_path="s3a://enterprise-datalake/silver/transactions",
            partition_date="2026-07-18"
        )
    except Exception as e:
        logger.error(f"Pipeline execution failure: {str(e)}")
        sys.exit(1)
    finally:
        spark.stop()

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.