Skill 54 · Ingesting Into Data Lake
Subchapter 54.10
references/glue-job-config.mdMarkdown9 KBView on GitHub
Guide for creating Glue jobs, configuring workers, advanced PySpark patterns, and monitoring for external data import pipelines.
Once you have the PySpark script saved to S3 (e.g., s3://<scripts-bucket>/glue-jobs/external-import-<table-name>.py), create the Glue job.
aws glue create-job \
--name "external-import-<source>-<table>" \
--role "<glue-role-arn>" \
--command "Name=glueetl,ScriptLocation=s3://<scripts-bucket>/glue-jobs/external-import-<table>.py,PythonVersion=3" \
--connections "Connections=<glue-connection-name>" \
--default-arguments '{
"--datalake-formats": "iceberg",
"--connection_name": "<glue-connection-name>",
"--source_table": "<schema>.<table>",
"--target_table": "<catalog>.<namespace>.<s3-table>",
"--watermark_column": "<timestamp-column>",
"--watermark_bucket": "<bucket>",
"--watermark_key": "watermarks/<table-name>.txt",
"--conf": "<see iceberg-catalog-config-and-usage.md for S3 Tables or standard Iceberg catalog config>",
"--enable-glue-datacatalog": "true",
"--enable-metrics": "true",
"--enable-continuous-cloudwatch-log": "true"
}' \
--glue-version "5.1" \
--number-of-workers 5 \
--worker-type "G.1X" \
--timeout 60 \
--max-retries 1 \
--region <region>Choose worker type based on workload characteristics:
| Worker Type | vCPUs | Memory | Use Case |
|---|---|---|---|
| G.1X | 4 | 16 GB | Standard ETL, small to medium data volumes |
| G.2X | 8 | 32 GB | Large data volumes, memory-intensive transforms |
| G.4X | 16 | 64 GB | Very large data volumes, complex joins |
| G.8X | 32 | 128 GB | Massive data volumes, high parallelism |
Number of workers guidance:
Start conservative and scale up based on job duration and throughput.
Set timeout based on expected job duration:
Add buffer for source database query time and network latency.
Configure retries for transient failures:
'MaxRetries': 1 # Retry once on failureFor production pipelines, consider:
MaxRetries to 1-2 for transient network issuesRequired arguments:
--datalake-formats iceberg: Required for S3 Tables and standard Iceberg targets--enable-glue-datacatalog: Enable Glue Data Catalog integration for Iceberg--conf: Spark catalog configuration. See iceberg-catalog-config-and-usage.md for the exact keys per target type.--enable-metrics: Publish CloudWatch metrics--enable-continuous-cloudwatch-log: Stream logs to CloudWatchOptional arguments:
--enable-spark-ui: Enable Spark UI for debugging (requires S3 bucket)--spark-event-logs-path: Where to store Spark UI logs--conf spark.sql.adaptive.enabled=true: Enable adaptive query execution--conf spark.sql.adaptive.coalescePartitions.enabled=true: Optimize partition countIf the source database is in a VPC, ensure the Glue job has network access:
'Connections': {
'Connections': ['<glue-connection-name>']
}The connection specifies:
Glue provisions ENIs in the specified subnet to access the database.
For large tables, read data in parallel using Spark partitioning:
# Read with parallel partitions
source_df = spark.read.format("jdbc").options(
url=jdbc_url,
dbtable="large_table",
numPartitions=10, # Read with 10 parallel connections
partitionColumn="id", # Partition on this column
lowerBound=1, # Min value
upperBound=10000000 # Max value
).load()This creates 10 parallel queries:
WHERE id >= 1 AND id < 1000000WHERE id >= 1000000 AND id < 2000000WHERE id >= 9000000 AND id <= 10000000Best practices:
numPartitions = number of workers × cores per workerlowerBound and upperBound based on actual data rangeIf there’s risk of duplicate records (job retries, late arrivals):
from pyspark.sql.window import Window
from pyspark.sql.functions import row_number
# Deduplicate by primary key, keeping latest by watermark
window = Window.partitionBy("primary_key").orderBy(col(watermark_column).desc())
deduplicated_df = source_df.withColumn("row_num", row_number().over(window)) \
.filter(col("row_num") == 1) \
.drop("row_num")Add data quality checks and type conversions:
from pyspark.sql.functions import col, when
transformed_df = source_df.select(
# Safe type casting with null handling
when(col("amount").cast("double").isNotNull(), col("amount").cast("double"))
.otherwise(0.0).alias("amount"),
# String trimming and validation
when(col("email").rlike(r"^[\w\.-]+@[\w\.-]+\.\w+$"), col("email"))
.otherwise(None).alias("email"),
# Date parsing with fallback
when(col("order_date").isNotNull(),
to_date(col("order_date"), "yyyy-MM-dd"))
.otherwise(None).alias("order_date")
)If source data can arrive late (event timestamp < updated timestamp):
from datetime import timedelta
# Load data from 1 day before last watermark to catch late arrivals
buffer_watermark = (datetime.strptime(last_watermark, '%Y-%m-%d %H:%M:%S')
- timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')
filtered_df = source_df.filter(
f"{args['watermark_column']} > '{buffer_watermark}'"
)
# Then use upsert to avoid duplicatesGlue streams job logs to CloudWatch Logs under:
/aws-glue/jobs/output<job-name>-<job-run-id>Key log patterns to monitor:
Last watermark: <value> - Starting point for incremental loadLoading X new/updated records - How many records foundUpdated watermark to: <value> - New watermark after loadERROR - Any errors during executionWith --enable-metrics, Glue publishes:
glue.driver.aggregate.numCompletedTasks - Tasks completedglue.driver.aggregate.elapsedTime - Job durationglue.driver.aggregate.recordsRead - Records read from sourceglue.driver.aggregate.bytesRead - Bytes read from sourceSet up CloudWatch alarms for:
Enable Spark UI for detailed execution metrics:
'DefaultArguments': {
'--enable-spark-ui': 'true',
'--spark-event-logs-path': 's3://<logs-bucket>/spark-logs/'
}Access via Glue console → Job runs → View Spark UI
Use Spark UI to:
Best practices for script management:
s3://<scripts-bucket>/glue-jobs/<job-name>.pyExample structure:
s3://my-glue-scripts/
prod/
external-import-customers.py
external-import-orders.py
dev/
external-import-customers.py
external-import-orders.pyTest PySpark scripts locally before deploying to Glue:
# Install dependencies
pip install pyspark boto3
# Run script locally (modify to use local Spark)
python external-import-customers.py \
--JOB_NAME test-run \
--connection_name test-connection \
--source_table customers \
--target_table local.test.customers \
--watermark_column updated_at \
--watermark_bucket test-bucket \
--watermark_key watermarks/customers.txtFor full local testing, use AWS Glue Docker images:
docker pull amazon/aws-glue-libs:glue_libs_5.0.0_image_01Glue ETL job creation workflow:
With a well-configured Glue job, external database data flows continuously into S3 Tables with minimal operational overhead.