Approach summary: use event-time 5-minute tumbling windows with a 10-minute watermark so late events within 10 minutes are included; aggregate per user_id; write Parquet files partitioned by date to S3 using checkpointing and append semantics. Ensure S3-commit compatibility (S3A/EMR committers) for atomic writes and enable a stable checkpointLocation for fault tolerance.python
from pyspark.sql import SparkSession
from pyspark.sql.functions import window, col, to_timestamp, date_format
spark = SparkSession.builder.appName("user-window-agg").getOrCreate()
# Read streaming source (e.g., Kafka)
raw = (spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "kafka:9092")
.option("subscribe", "events")
.load())
events = (raw.selectExpr("CAST(value AS STRING) as json")
.selectExpr("from_json(json, 'user_id STRING, event_ts STRING, value DOUBLE') as v")
.selectExpr("v.user_id", "to_timestamp(v.event_ts) as event_time", "v.value"))
# Windowed aggregation with 5-minute tumbling windows and 10-minute watermark
agg = (events
.withWatermark("event_time", "10 minutes") # drop events later than watermark
.groupBy(col("user_id"),
window(col("event_time"), "5 minutes")) # tumbling window
.agg({"value": "sum", "*": "count"})
.withColumn("window_start", col("window").start)
.withColumn("window_end", col("window").end)
.withColumn("event_date", date_format(col("window_start"), "yyyy-MM-dd")))
# Write to S3 as partitioned Parquet with checkpointing
(out := agg.writeStream
.format("parquet")
.outputMode("append") # append is appropriate with watermark
.option("path", "s3a://my-bucket/path/to/output/")
.option("checkpointLocation", "s3a://my-bucket/checkpoints/user-window-agg/")
.partitionBy("event_date") # partition files by date
.trigger(processingTime="1 minute")
.start())
Key points / reasoning:- withWatermark("event_time","10 minutes") allows late events up to 10 minutes; events later are ignored.- Tumbling window of 5 minutes via window(...,"5 minutes") groups event_time into fixed 5-min buckets.- outputMode "append" works because watermark + windowed aggregation lets Spark emit results once windows are complete; using "complete" would rewrite full state each trigger and isn't supported for file sinks.- CheckpointLocation is required for exactly-once recovery of streaming job state; for S3, use S3A/EMR committers or a durable filesystem to ensure atomic file commits.- Monitor state size (late threshold) and set spark.sql.streaming.stateStore.maintenanceInterval / state retention as needed.