Cloud Platforms
AWS Lambda for Data Engineers
Limits and invocation models, S3/SQS/Kinesis handler examples, packaging and deployment, configuration and secrets, idempotency, observability and anti-patterns.
Contents
1. What AWS Lambda Is
2. Invocation Models
3. Example: Validate a File and Start a Pipeline
4. Example: Batch Processing from SQS
5. Example: Kinesis Stream Consumer
6. Packaging & Deployment
7. Configuration, Secrets & Networking
8. Idempotency, Observability & Anti-Patterns
9. Common Interview Questions
1. What AWS Lambda Is
AWS Lambda runs your function in response to events without servers to manage. You pay per request and per GB-second of execution. In data engineering it is the glue for short, event-driven steps: validate a file, start a job, route a message, call an API, send an alert.
| Limit | Value |
|---|---|
| Maximum run time | 15 minutes per invocation |
| Memory | 128 MB – 10,240 MB (CPU scales with memory; about 1 vCPU at 1,769 MB) |
| Ephemeral /tmp storage | 512 MB by default, configurable up to 10 GB |
| Deployment package | 50 MB zipped upload (250 MB unzipped, including layers); container images up to 10 GB |
| Concurrency | 1,000 concurrent executions per region by default (can be raised) |
| Payload | 6 MB for synchronous requests; asynchronous events are smaller (check current docs) |
2. Invocation Models
| Model | Examples | Retries and failures |
|---|---|---|
| Synchronous | API Gateway, function URLs, SDK invoke | Caller sees the error and decides |
| Asynchronous | S3 notifications, SNS, EventBridge | Lambda retries twice; then send to an on-failure destination or DLQ |
| Poll-based (event source mapping) | SQS, Kinesis, DynamoDB Streams, MSK/Kafka | Lambda polls and batches; failures retry per batch, with partial-batch responses, bisecting and DLQs |
3. Example: Validate a File and Start a Pipeline
import json, os, urllib.parse
import boto3
s3 = boto3.client("s3")
sfn = boto3.client("stepfunctions")
STATE_MACHINE_ARN = os.environ["STATE_MACHINE_ARN"] # configuration via environment variables
MAX_BYTES = 5 * 1024 ** 3
def handler(event, context):
for record in event["Records"]: # one S3 event can contain several records
bucket = record["s3"]["bucket"]["name"]
key = urllib.parse.unquote_plus(record["s3"]["object"]["key"]) # keys arrive URL-encoded
head = s3.head_object(Bucket=bucket, Key=key)
if not key.endswith(".csv.gz") or head["ContentLength"] == 0 or head["ContentLength"] > MAX_BYTES:
s3.copy_object(Bucket=bucket, Key=f"_quarantine/{key}", CopySource={"Bucket": bucket, "Key": key})
print(json.dumps({"level": "WARN", "msg": "rejected file", "key": key}))
continue
sfn.start_execution(
stateMachineArn=STATE_MACHINE_ARN,
name=f"load-{head['ETag'].strip(chr(34))}", # same file → same name → no duplicate run
input=json.dumps({"bucket": bucket, "key": key, "size": head["ContentLength"]}),
)
return {"status": "ok"}
Using the object ETag in the execution name makes the trigger idempotent: if S3 delivers the same event twice, Step Functions rejects the duplicate name for a Standard workflow.
4. Example: Batch Processing from SQS
import json
def handler(event, context):
failures = []
for record in event["Records"]:
try:
msg = json.loads(record["body"])
process(msg) # your logic; must be idempotent
except Exception as exc:
print(f"failed {record['messageId']}: {exc}")
failures.append({"itemIdentifier": record["messageId"]})
# only the failed messages return to the queue; the rest are deleted
return {"batchItemFailures": failures}
| Event source mapping setting | Purpose |
|---|---|
| Batch size / batching window | How many records per invocation, and how long to wait to fill a batch |
| ReportBatchItemFailures | Enables the partial-batch response shown above |
| Maximum concurrency (SQS) | Caps parallel invocations to protect downstream databases |
| Bisect batch on error (streams) | Splits a failing Kinesis/DynamoDB batch to isolate the poison record |
| Destinations / DLQ | Where records go after all retries fail |
Set the SQS visibility timeout to at least six times the function timeout, or messages reappear while still being processed.
5. Example: Kinesis Stream Consumer
import base64, json
def handler(event, context):
rows = []
for record in event["Records"]:
payload = base64.b64decode(record["kinesis"]["data"]) # data arrives base64-encoded
txn = json.loads(payload)
if txn.get("amount", 0) > 100000:
rows.append(txn)
if rows:
write_alerts(rows) # e.g. DynamoDB batch_writer
return {"batchItemFailures": []}
6. Packaging & Deployment
# zip deployment with dependencies
pip install -r requirements.txt -t package/
cp handler.py package/ && (cd package && zip -r ../function.zip .)
aws lambda create-function --function-name file-validator \
--runtime python3.12 --handler handler.handler --architectures arm64 \
--role arn:aws:iam::111122223333:role/file-validator-role \
--memory-size 512 --timeout 60 --zip-file fileb://function.zip \
--environment "Variables={STATE_MACHINE_ARN=arn:aws:states:ap-south-1:111122223333:stateMachine:daily-load}"
aws lambda update-function-code --function-name file-validator --zip-file fileb://function.zip
aws lambda invoke --function-name file-validator --payload fileb://test_event.json out.json
- Layers share dependencies across functions; AWS publishes a managed layer for the AWS SDK for pandas (pandas, pyarrow, awswrangler).
- Container images (up to 10 GB) for heavy dependencies or native libraries.
- Deploy with infrastructure as code (SAM, CDK, CloudFormation, Terraform) rather than the console.
- arm64 (Graviton) functions are typically about 20% cheaper with similar or better performance.
7. Configuration, Secrets & Networking
| Topic | Guidance |
|---|---|
| Execution role | Least privilege: only the S3 prefixes, queues and APIs the function needs |
| Secrets | Read from Secrets Manager or Parameter Store at cold start and cache in a global variable; never put secrets in plain environment variables |
| VPC access | Attach to private subnets to reach RDS or internal services; add VPC endpoints or a NAT gateway for AWS APIs and the internet |
| Database connections | Use RDS Proxy: thousands of concurrent functions would otherwise exhaust database connections |
| Reserved concurrency | Guarantees and caps concurrency for one function (also a quick "off switch" at 0) |
| Provisioned concurrency / SnapStart | Pre-initialised environments to remove cold starts for latency-sensitive paths |
| Timeout | Set close to real need; the default is only 3 seconds |
8. Idempotency, Observability & Anti-Patterns
- Every event source can deliver the same event more than once, so make handlers idempotent: conditional writes, deterministic IDs, or the Powertools for AWS Lambda idempotency utility backed by DynamoDB.
- Use Powertools for AWS Lambda (Python) for structured logging, metrics and tracing; enable X-Ray for request traces.
- Alarm on
Errors,Throttles,Durationnear the timeout,IteratorAge(streams) and DLQ depth.
| Anti-pattern | Do instead |
|---|---|
| Heavy ETL or large joins in Lambda | Glue, EMR, or Fargate; Lambda only starts them |
| Work that may exceed 15 minutes | Step Functions orchestrating Glue/EMR/Batch, or split the work (Distributed Map) |
| A function that invokes itself in a loop | Step Functions; AWS detects and stops recursive loops |
| Writing back into the prefix that triggers the function | Write to a different prefix or bucket |
| Opening a new DB connection per invocation | Reuse connections outside the handler, and use RDS Proxy |
9. Common Interview Questions
| Question | Short answer |
|---|---|
| What are Lambda's key limits? | 15-minute timeout, up to 10 GB memory and /tmp, deployment size limits, payload limits and account concurrency |
| How do you handle work longer than 15 minutes? | Hand it to Glue, EMR, Batch or Fargate and orchestrate with Step Functions, or split it into smaller parallel units |
| What is a cold start, and how do you reduce it? | Initialising a new execution environment; reduce with smaller packages, lazy imports, provisioned concurrency or SnapStart |
| How do retries work for async invocations? | Two retries with backoff, then an on-failure destination or DLQ; for SQS/stream sources, retries are per batch |
| What is a partial batch response? | Returning batchItemFailures so only failed SQS/stream records are retried instead of the whole batch |
| How do you connect Lambda to RDS safely? | Run in the VPC, use RDS Proxy for connection pooling, IAM authentication or Secrets Manager credentials |
| How do you make a Lambda idempotent? | Deterministic keys and conditional writes, or record processed event IDs in DynamoDB before acting |