원클릭으로
spark-pipeline
Design and implement Apache Spark data pipelines — batch and streaming ETL
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Design and implement Apache Spark data pipelines — batch and streaming ETL
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Consult and write the ARAYA postoffice — the operational-directive channel. Advisory, never a gate: read it at cycle start, append your entry at cycle end, consider its directives, never be blocked by it. Governance acts never travel the postoffice.
Audit and enforce brand compliance across all projects and platforms — logo,
Design REST and GraphQL APIs following OpenAPI 3.1 standards. Produce complete
Connect frontend to backend — type-safe API clients, request/response handling,
Implement authentication and authorization middleware — JWT validation, role-based
Design scalable component architectures — design systems, component libraries,
| name | spark-pipeline |
| description | Design and implement Apache Spark data pipelines — batch and streaming ETL |
| governance | Constitution ENG-004: Engineering Excellence & Software Craftsmanship Standard |
Design and implement Apache Spark data pipelines — batch and streaming ETL using PySpark/SparkSQL — following medallion architecture patterns with validation, error handling, and production-grade reliability.
Data pipelines that "work on sample data" fail in production with null values, schema drift, and silent data loss. This skill produces Spark pipelines with schema enforcement, audit columns, error handling, and idempotent writes — reliable enough for production data engineering.
When building ETL/ELT pipelines for data lakes. When processing large datasets (> 1GB) that require distributed processing. When implementing medallion architecture (Bronze → Silver → Gold).
Data source details (CSV, JSON, Parquet, JDBC, Kafka), target schema, business transformation rules, partitioning requirements.
# spark_jobs/bronze_ingestion.py
from pyspark.sql import SparkSession, DataFrame
from pyspark.sql.types import StructType, StructField, StringType, DoubleType
from pyspark.sql.functions import input_file_name, current_timestamp, lit
from datetime import datetime
spark = SparkSession.builder \
.appName("Bronze Ingestion — Trades") \
.config("spark.sql.sources.partitionOverwriteMode", "dynamic") \
.getOrCreate()
# Define explicit schema (inferSchema=False — schema is explicit)
trade_schema = StructType([
StructField("trade_id", StringType(), False),
StructField("customer_id", StringType(), False),
StructField("coin_id", StringType(), False),
StructField("trade_type", StringType(), False),
StructField("quantity", DoubleType(), False),
StructField("price", DoubleType(), False),
StructField("trade_date", StringType(), False),
])
class BronzeIngestion:
"""Ingest raw data into Bronze layer — append-only, immutable."""
def __init__(self, source_path: str, bronze_path: str):
self.source_path = source_path
self.bronze_path = bronze_path
self.ingestion_ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def read_source(self) -> DataFrame:
"""Read source data with explicit schema."""
return (
spark.read
.option("header", "false") # CSV has no header
.option("delimiter", ";") # Semicolon-delimited
.schema(trade_schema) # Enforce schema
.csv(self.source_path)
)
def add_audit_columns(self, df: DataFrame) -> DataFrame:
"""Add audit columns required on every table."""
return df.withColumns({
"record_source": input_file_name(),
"ingestion_timestamp": current_timestamp(),
"source_file": input_file_name(),
})
def validate_row_counts(self, df: DataFrame, source_count: int) -> None:
"""Validate ingestion row count matches source."""
bronze_count = df.count()
if bronze_count != source_count:
raise ValueError(
f"Row count mismatch: source={source_count}, bronze={bronze_count}. "
f"Difference: {abs(source_count - bronze_count)} rows"
)
print(f"✅ Row count validated: {bronze_count:,} rows ingested")
def write_bronze(self, df: DataFrame) -> None:
"""Write to Bronze layer — append-only, partitioned by date."""
(
df.write
.mode("append") # Append-only — never overwrite Bronze
.format("parquet")
.partitionBy("ingestion_date")
.option("compression", "snappy")
.save(self.bronze_path)
)
def run(self) -> None:
"""Execute full Bronze ingestion pipeline."""
print(f"🔄 Starting Bronze ingestion: {self.ingestion_ts}")
# 1. Read source
raw_df = self.read_source()
source_count = raw_df.count()
print(f"📥 Source rows: {source_count:,}")
# 2. Add audit columns
enriched_df = self.add_audit_columns(raw_df)
# 3. Add ingestion date for partitioning
enriched_df = enriched_df.withColumn(
"ingestion_date", current_timestamp().cast("date")
)
# 4. Validate
self.validate_row_counts(enriched_df, source_count)
# 5. Write
self.write_bronze(enriched_df)
print(f"✅ Bronze ingestion complete: {self.bronze_path}")
# --- Usage ---
if __name__ == "__main__":
ingestion = BronzeIngestion(
source_path="./data/trades/",
bronze_path="./data/bronze/trades/"
)
ingestion.run()
record_source, ingestion_timestamp, source_fileinferSchema=False — schema must be explicit; inferred schemas cause production failuresrecord_source, ingestion_timestamp, source_file