Skill 54 · Ingesting Into Data Lake
Subchapter 54.23
references/testing-and-scheduling.mdMarkdown14 KBView on GitHub
Complete guide for testing Glue ETL jobs, validating data loads, and setting up recurring schedules for external data import pipelines.
After creating a Glue ETL job, you must:
Before scheduling, run the job once to validate the entire workflow.
JOB_RUN_ID=$(aws glue start-job-run \
--job-name "external-import-<source>-<table>" \
--region <region> \
--query 'JobRunId' --output text)
echo "Job run started: $JOB_RUN_ID"Check job status and logs:
# Get job run status
aws glue get-job-run \
--job-name "external-import-<source>-<table>" \
--run-id "$JOB_RUN_ID" \
--region <region>
# Check if job succeeded
STATUS=$(aws glue get-job-run \
--job-name "external-import-<source>-<table>" \
--run-id "$JOB_RUN_ID" \
--query 'JobRun.JobRunState' \
--output text)
echo "Job status: $STATUS"Job states:
STARTING - Job is initializingRUNNING - Job is executingSUCCEEDED - Job completed successfullyFAILED - Job failed (check logs for errors)TIMEOUT - Job exceeded timeout limitSTOPPED - Job was manually stoppedGlue streams logs to CloudWatch Logs:
# Get log stream name
LOG_STREAM=$(aws glue get-job-run \
--job-name "external-import-<source>-<table>" \
--run-id "$JOB_RUN_ID" \
--query 'JobRun.LogGroupName' \
--output text)
# Tail logs
aws logs tail /aws-glue/jobs/output --follow \
--log-stream-names "<job-name>-<run-id>" \
--region <region>Key log messages to look for:
Last watermark: <value> - Starting point for incremental loadLoading X new/updated records - Number of records foundUpdated watermark to: <value> - New watermark after successful loadSuccessfully loaded X records - Confirmation of append/upsertERROR or Exception - Errors that caused failureSymptom: Job fails with “Connection timeout” or “Unable to connect to database”
Causes:
Solution:
Symptom: “Access denied” or “Invalid username/password”
Causes:
Solution:
Symptom: “Type mismatch” or “Cannot cast X to Y”
Causes:
Solution:
.cast("string") as fallback for problematic columnswhen(col("x").isNotNull(), col("x")).otherwise(default_value)Symptom: Job runs slowly or times out
Causes:
Solution:
numPartitions optionSymptom: Job runs but no new records loaded, watermark stays same
Causes:
Solution:
After the job completes successfully, verify data was loaded correctly.
Query the target S3 Table to confirm records were written:
-- Count total rows
SELECT COUNT(*) FROM "<catalog>"."<namespace>"."<table>";Compare with expected count from job logs (e.g., “Successfully loaded X records”).
View the most recently loaded records:
-- Get latest records by watermark column
SELECT *
FROM "<catalog>"."<namespace>"."<table>"
ORDER BY <watermark-column> DESC
LIMIT 10;Verify:
Check that the watermark file was updated:
# Read watermark file from S3
aws s3 cp s3://<bucket>/watermarks/<table-name>.txt -
# Should show the new watermark value matching the job logsFor critical tables, compare aggregations between source and target:
Source (via Glue connection):
SELECT COUNT(*), SUM(amount), MAX(updated_at)
FROM <schema>.<table>
WHERE updated_at > '<last-watermark>';Target (S3 Table):
SELECT COUNT(*), SUM(amount), MAX(load_timestamp)
FROM "<catalog>"."<namespace>"."<table>"
WHERE load_timestamp >= '<job-start-time>';Counts and sums should match.
Run basic data quality checks:
-- Check for NULL values in key columns
SELECT COUNT(*) FROM "<catalog>"."<namespace>"."<table>"
WHERE customer_id IS NULL OR email IS NULL;
-- Check for duplicates (if using append instead of upsert)
SELECT customer_id, COUNT(*)
FROM "<catalog>"."<namespace>"."<table>"
GROUP BY customer_id
HAVING COUNT(*) > 1;
-- Check date range
SELECT MIN(order_date), MAX(order_date)
FROM "<catalog>"."<namespace>"."<table>";For production pipelines, consider using AWS Glue Data Quality rules to automate validation.
Once testing is complete, set up scheduling for ongoing data syncs.
Choose schedule based on data freshness requirements:
Real-time (<1 minute latency):
Near real-time (5-15 minute latency):
cron(0/15 * * * ? *)Hourly:
cron(0 * * * ? *)Every 6 hours:
cron(0 */6 * * ? *)Daily:
cron(0 2 * * ? *)Weekly:
cron(0 2 ? * MON *)Coordinate with source system:
Glue Triggers schedule job execution.
aws glue create-trigger \
--name "external-import-<table>-schedule" \
--type SCHEDULED \
--schedule "cron(0 */6 * * ? *)" \
--actions JobName="external-import-<source>-<table>" \
--description "Scheduled sync from <source> to S3 Tables" \
--start-on-creation \
--region <region>Cron expression format:
cron(Minutes Hours Day-of-month Month Day-of-week Year)Examples:
cron(0/15 * * * ? *)cron(0 * * * ? *)cron(0 */6 * * ? *)cron(0 2 * * ? *)cron(0 6 ? * MON-FRI *)cron(0 0 1 * ? *)Start a trigger (enable scheduling):
aws glue start-trigger \
--name "external-import-<table>-schedule" \
--region <region>Stop a trigger (disable scheduling):
aws glue stop-trigger \
--name "external-import-<table>-schedule" \
--region <region>Check trigger details and recent runs:
aws glue get-trigger \
--name "external-import-<table>-schedule" \
--region <region>Set up CloudWatch alarms for job failures:
# Create alarm for job failures
aws cloudwatch put-metric-alarm \
--alarm-name "glue-job-failure-<table>" \
--alarm-description "Alert when Glue job fails" \
--metric-name JobFailure \
--namespace AWS/Glue \
--statistic Sum \
--period 300 \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold \
--dimensions Name=JobName,Value="external-import-<source>-<table>" \
--evaluation-periods 1 \
--alarm-actions <sns-topic-arn>Metrics to monitor:
glue.driver.aggregate.recordsRead - Records read from sourceglue.driver.aggregate.elapsedTime - Job durationList recent executions of a job:
aws glue get-job-runs \
--job-name "external-import-<source>-<table>" \
--region <region> \
--max-results 10Monitor how watermark advances over time:
# List watermark history (if versioning enabled on S3 bucket)
aws s3api list-object-versions \
--bucket <bucket> \
--prefix watermarks/<table-name>.txt \
--query 'Versions[*].[LastModified,VersionId]' \
--output tableCreate a Lambda function to log watermark values to CloudWatch Logs after each job run for historical tracking.
Run a job only after another job succeeds:
aws glue create-trigger \
--name "external-import-orders-after-customers" \
--type CONDITIONAL \
--actions JobName="external-import-orders" \
--predicate '{
"Conditions": [{
"LogicalOperator": "EQUALS",
"JobName": "external-import-customers",
"State": "SUCCEEDED"
}]
}' \
--start-on-creationUse for:
Trigger job based on EventBridge events:
# Create EventBridge rule to trigger Glue job
aws events put-rule \
--name "trigger-glue-on-event" \
--event-pattern '{
"source": ["aws.s3"],
"detail-type": ["Object Created"],
"detail": {
"bucket": {
"name": ["source-data-bucket"]
}
}
}'
aws events put-targets \
--rule "trigger-glue-on-event" \
--targets "Id=1,Arn=arn:aws:glue:region:account:job/external-import-job"Allow users to trigger jobs manually via API/console without scheduling:
# Don't create a trigger, just run the job when needed
aws glue start-job-run \
--job-name "external-import-<source>-<table>"Testing and scheduling workflow:
With proper testing and monitoring, scheduled Glue jobs provide reliable, automated data pipelines from external databases to S3 Tables.