ETL / data
Replace fragile nightly SFTP flat-file jobs with an event-driven pipeline that validates, dedupes, and processes files as they land.
The nightly SFTP drop is where integrations quietly rot: a malformed file, a missed run, or a partial upload and downstream is silently wrong until someone notices. An event-driven pipeline picks up each file the moment it lands, validates and dedupes it, and processes it with retries and a dead-letter path — so failures are visible and recoverable, not discovered a week later.
A working piece from this integration — no sign-up. The full build handles the edge cases, safeguards, and cutover.
# Event-driven SFTP file ingestion — Lambda/Cloud Function pattern # Triggered on S3 PUT (files transferred from SFTP) or equivalent event grid def handle_file_event(event, ctx): bucket, key = event['bucket'], event['key'] # 1. Read & validate file structure raw = s3.get_object(Bucket=bucket, Key=key)['Body'].read() records = parse_csv(raw) if not validate_header(records[0'], REQUIRED_COLS): raise ValueError(f"Invalid schema in {key}") # 2. Dedupe by content hash (stored in DynamoDB) content_hash = hashlib.sha256(raw).hexdigest() if dynamodb.get_item(Key={'hash': content_hash}).get('Item'): logger.info(f"Skipping duplicate {key}") return {"status": "duplicate_skipped"} # 3. Validate & publish each record as event for i, row in enumerate(records[1:], start=2): if is_valid(row): sns.publish( TopicArn=STREAM_TOPIC, Message=json.dumps({"source_file": key, "line": i, "data": row, "ts": datetime.utcnow().isoformat()}) ) # 4. Mark hash processed dynamodb.put_item(Item={'hash': content_hash, 'key': key, 'ts': datetime.utcnow().isoformat()}) return {"status": "processed", "records": len(records)-1}
How we'd take this from discovery to a production-safe cutover — the phases, the canonical mapping, and the edge cases that bite.
Replace the fragile nightly SFTP flat-file batch with an event-driven pipeline that triggers on file arrival, validates against a defined schema, deduplicates using a state store, and publishes structured events downstream.
/inbound/datafeed, /inbound/transactions, etc.). Document record counts, file naming conventions, and the downstream SQL inserts or API calls each job performs. This becomes your regression baseline.sftp-land-zone) with a transfer prefix (/inbound/). Mirror SFTP writes using rsync in the interim, or wire the SFTP server to push directly to S3 if supported. Files now have a durable, versioned artifact with a checksum attribute.sftp-land-zone to an EventBridge rule (sftp-file-arrival) that filters on *.csv and *.dat suffixes. The rule invokes a Lambda function (datafeed-file-trigger) or publishes to an SQS queue (datafeed-ingestion-queue) for durability./schemas/datafeed/v2.json). Reject non-conforming files to sftp-rejected/ and increment a validation_errors CloudWatch metric.SHA256) and query a DynamoDB table (file_processing_state) keyed on checksum. If the record exists with status = COMPLETED, skip processing and log a DUPLICATE_FILE event. Insert the checksum with status = IN_PROGRESS at transaction start; update to COMPLETED or FAILED on exit.eventId, sourceFile, receivedAt, recordCount, and a payload array. Publish to an EventBridge custom bus (datafeed-events) or SNS topic (datafeed-ingested) for downstream consumers. Write a processing audit record to processing_audit_log.file_received_count, processing_duration_seconds, record_published_count) and stream logs to a centralized aggregator. Configure alarms on dlq-message-count exceeding zero and on p95 processing latency above 60 seconds.| Source File Field | Target Event Field | Type | Notes |
|---|---|---|---|
FILE_ID (col 1) |
sourceFile |
String | Unique file identifier from filename |
RECORD_SEQ (col 2) |
payload[].sequence |
Integer | Row number within file |
RECORD_TYPE (col 3) |
payload[].recordType |
String | Determines downstream routing |
EFFECTIVE_DATE (col 4) |
payload[].effectiveDate |
ISO 8601 Date | Parsed from YYYYMMDD |
AMOUNT (col 5) |
payload[].amount |
Decimal | Scaled by 100 (stored as integer) |
ACCOUNT_NUM (col 6) |
payload[].accountId |
String | Masked to last 4 digits |
| File checksum | checksum |
String | SHA256, used for dedup |
COMPLETED. On retry, the in-flight record triggers a ConditionalCheckFailedException; handle it by re-querying status and resuming from the last committed offset.sftp-dead-letter/ with an alert.EFFECTIVE_DATE are valid upstream data but may cause downstream reconciliation failures. Tag these events with temporalAnomaly: true for manual review.staging_datafeed). Use a reconciliation query that compares record counts and checksummed sums grouped by sourceFile and effectiveDate between the two schemas. Any mismatch halts the cutover and pages the on-call engineer. Once the delta is zero for 14 consecutive days, promote the shadow schema, enable the DLQ alerts as production-grade, and archive the legacy job with a 90-day retention tag. If a rollback is required, re-enable the disabled cron schedule and repoint consumers to the original tables; the dedup table ensures no double-counting during the reversal window. Reading the reference is free. Delivering it under liability — with the safeguards that keep production running through the cutover — is what we do.