Skill 54 · Ingesting Into Data Lake
Subchapter 54.15
references/jdbc-performance.mdMarkdown10 KBView on GitHub
Guide for diagnosing and resolving performance issues, incremental loading problems, IAM/permissions errors, and monitoring for external data import pipelines.
Symptom:
Root causes:
Troubleshooting:
Check query execution plan in source database:
-- Oracle
EXPLAIN PLAN FOR
SELECT * FROM large_table WHERE updated_at > '2024-01-01';
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- SQL Server
SET SHOWPLAN_TEXT ON;
SELECT * FROM large_table WHERE updated_at > '2024-01-01';
-- PostgreSQL
EXPLAIN ANALYZE
SELECT * FROM large_table WHERE updated_at > '2024-01-01';
-- MySQL
EXPLAIN
SELECT * FROM large_table WHERE updated_at > '2024-01-01';Monitor source database load:
Measure network throughput:
Solutions:
Add index on watermark column:
-- Oracle
CREATE INDEX idx_updated_at ON large_table(updated_at);
-- SQL Server
CREATE INDEX idx_updated_at ON large_table(updated_at);
-- PostgreSQL
CREATE INDEX idx_updated_at ON large_table(updated_at);
-- MySQL
CREATE INDEX idx_updated_at ON large_table(updated_at);Use parallel reads:
source_df = spark.read.format("jdbc").options(
url=jdbc_url,
dbtable="large_table",
numPartitions=10, # Read in parallel
partitionColumn="id",
lowerBound=1,
upperBound=10000000
).load()Reduce batch size:
# Load 1 day at a time instead of full month
WHERE updated_at >= '2024-01-01' AND updated_at < '2024-01-02'Increase Glue workers:
'NumberOfWorkers': 20, # Up from 5
'WorkerType': 'G.2X' # Larger workersSymptom:
ERROR: Job exceeded timeout of 60 minutes
JobRunState: TIMEOUTRoot causes:
Solution:
Increase job timeout:
aws glue update-job \
--job-name external-import-customers \
--job-update Timeout=180Symptom:
Root causes:
>= instead of >Troubleshooting:
Check source for new data:
SELECT COUNT(*) FROM table WHERE updated_at > '<last-watermark>';Check timezone:
print(f"Last watermark: {last_watermark}")
print(f"Last watermark timezone: {last_watermark_tz}")
# Convert to UTC
from datetime import datetime
import pytz
utc_watermark = pytz.timezone('America/New_York').localize(
datetime.strptime(last_watermark, '%Y-%m-%d %H:%M:%S')
).astimezone(pytz.utc)Check filter logic:
# Correct: > (strictly greater than)
filtered_df = source_df.filter(f"{watermark_column} > '{last_watermark}'")
# Incorrect: >= (will reload last batch every time)
# filtered_df = source_df.filter(f"{watermark_column} >= '{last_watermark}'")Solution:
Normalize all timestamps to UTC:
from pyspark.sql.functions import to_utc_timestamp
# Convert source timestamp to UTC
df_utc = source_df.withColumn(
"updated_at_utc",
to_utc_timestamp(col("updated_at"), "America/New_York")
)
# Filter using UTC timestamps
filtered_df = df_utc.filter(f"updated_at_utc > '{last_watermark_utc}'")Symptom:
Root causes:
Solution:
Use upsert for mutable data:
# MERGE INTO instead of append
spark.sql(f"""
MERGE INTO {target_table} AS target
USING source_view AS source
ON target.customer_id = source.customer_id
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *
""")Add deduplication logic:
from pyspark.sql.window import Window
from pyspark.sql.functions import row_number
window = Window.partitionBy("customer_id").orderBy(col("updated_at").desc())
deduplicated_df = source_df.withColumn("row_num", row_number().over(window)) \
.filter(col("row_num") == 1) \
.drop("row_num")Handle late arrivals with buffer:
# Load from 1 day before watermark
buffer_watermark = last_watermark - timedelta(days=1)
filtered_df = source_df.filter(f"{watermark_column} > '{buffer_watermark}'")
# Then upsert to avoid duplicatesSymptom:
ERROR: Access Denied (Service: Amazon S3; Status Code: 403)Root cause: Glue job IAM role lacks S3 permissions
Solution:
Add S3 permissions to Glue role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::<scripts-bucket>/*",
"arn:aws:s3:::<watermark-bucket>/*",
"arn:aws:s3:::<data-bucket>/*"
]
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": [
"arn:aws:s3:::<scripts-bucket>",
"arn:aws:s3:::<watermark-bucket>",
"arn:aws:s3:::<data-bucket>"
]
}
]
}Symptom:
ERROR: User is not authorized to perform glue:GetTableRoot cause: Glue job role lacks Glue Data Catalog permissions
Solution:
Add Glue permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"glue:GetDatabase",
"glue:GetTable",
"glue:GetPartitions",
"glue:CreateTable",
"glue:UpdateTable",
"glue:DeleteTable"
],
"Resource": [
"arn:aws:glue:region:account:catalog",
"arn:aws:glue:region:account:database/*",
"arn:aws:glue:region:account:table/*/*"
]
}
]
}Job failure alarm:
aws cloudwatch put-metric-alarm \
--alarm-name "glue-job-failure-customers" \
--metric-name JobFailure \
--namespace AWS/Glue \
--statistic Sum \
--period 300 \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold \
--dimensions Name=JobName,Value="external-import-customers" \
--evaluation-periods 1 \
--alarm-actions <sns-topic-arn>Long-running job alarm:
aws cloudwatch put-metric-alarm \
--alarm-name "glue-job-long-running-customers" \
--metric-name glue.driver.aggregate.elapsedTime \
--namespace Glue \
--statistic Maximum \
--period 300 \
--threshold 3600000 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=JobName,Value="external-import-customers" \
--evaluation-periods 1 \
--alarm-actions <sns-topic-arn>glue.driver.aggregate.recordsRead - Records read from sourceglue.driver.aggregate.bytesRead - Bytes read from sourceglue.driver.aggregate.elapsedTime - Job durationglue.driver.aggregate.numCompletedTasks - Tasks completedWhen a job fails, follow this systematic approach:
aws glue get-job-run \
--job-name <job-name> \
--run-id <run-id> \
--query 'JobRun.[JobRunState,ErrorMessage]'aws logs tail /aws-glue/jobs/output --follow \
--log-stream-names "<job-name>-<run-id>"Look for:
ERROR messages# Test Glue connection
aws glue get-connection --name <connection-name>
# Test from EC2 in same subnet
telnet <db-host> <db-port># Check IAM role policies
aws iam get-role --role-name <glue-role-name>
aws iam list-attached-role-policies --role-name <glue-role-name>-- Run query in source database
SELECT COUNT(*) FROM table WHERE updated_at > '<watermark>';# Read watermark file
aws s3 cp s3://<bucket>/watermarks/<table>.txt -Error resolution workflow:
With systematic troubleshooting and proper monitoring, external data import pipelines run reliably with minimal intervention.