AWS Builder Center
Routing Glacial Floods for $0.00076 Per Run Using AWS Step Functions and Lambda

Routing Glacial Floods for $0.00076 Per Run Using AWS Step Functions and Lambda

How I built CASCADE-GLOF, a serverless telemetry engine on AWS that predicts glacial lake outburst floods using Sentinel-2 satellite data and Muskingum-Cunge routing for under a tenth of a cent per execution.

1. The 2023 Sikkim flood

On the night of 3 October 2023, the moraine dam holding South Lhonak Lake in north Sikkim failed. Seismic records place the collapse at 22:12 IST. About 50 million m³ of water drained into the Teesta valley. Roughly 2 hours 18 minutes later it reached the 1,200 MW Teesta-III dam at Chungthang, 67.5 km downstream, and destroyed it.
Before it is a modelling problem, a glacial lake outburst flood (GLOF) is a screening problem. An operator below a glacial lake needs two numbers in advance: how large the flood could be, and how many minutes of warning the valley gets. CASCADE produces those numbers from public satellite data, and I validated it by replaying the 2023 event.
The workload has three distinct phases:
  1. Lake measurement: embarrassingly parallel, one job per lake-month, I/O-bound reads of satellite imagery.
  1. Flood routing: CPU-bound numerics per lake.
  1. Publishing: a small aggregation step.

2. The serverless architecture

Everything is one AWS SAM template: 30 resources in us-west-2, deliberately co-located with the Sentinel-2 L2A Cloud-Optimized GeoTIFFs on the Registry of Open Data on AWS.
Orchestration. A Standard AWS Step Functions workflow runs  PlanJobs → MeasureMonths → RouteFloods → PublishDossier → Notify .
  •  MeasureMonths  is an inline Map over one item per lake-month, capped at 8 concurrent iterations.
  •  RouteFloods  maps over lakes.
JSON
"MeasureMonths": {
"Type": "Map",
"ItemsPath": "$.plan.items",
"MaxConcurrency": 8,
"ItemProcessor": { "ProcessorConfig": { "Mode": "INLINE" }, "StartAt": "MeasureLakeMonth", "States": { ... } },
"ResultPath": "$.observations"
}
Compute. A single container image in Amazon ECR serves four Lambda functions.
  • The image: Python 3.14 with rasterio/GDAL, pyproj and shapely; 1.18 GB uncompressed.
  • The functions differ only in  ImageConfig.Command , memory and timeout.  measure  gets 3,008 MB and 900 s;  route  gets 2,048 MB and 300 s;  plan  and  report  get 512 MB.
Data access. GDAL runs with  AWS_NO_SIGN_REQUEST=YES  and  GDAL_DISABLE_READDIR_ON_OPEN=EMPTY_DIR . Each job issues HTTP range reads for only the window around the lake's 10 m UTM grid; no scene is ever downloaded. The Copernicus DEM is read once per lake, and the traced flow path and cross-sections are cached in Amazon S3.
State and security.
  • Storage: observations go to on-demand Amazon DynamoDB. Results go to a private, TLS-only S3 bucket.
  • IAM: each function has its own role, scoped to specific S3 prefixes and  dynamodb:PutItem .
Failure semantics.
  • Retries: tasks retry transient Lambda errors with exponential backoff and full jitter.  measure  also retries  States.TaskFailed  once after 30 s.
  • Catches: a Catch records a failed month, and the run continues.
  • Alerts: the alert step uses the native  sns:publish  integration with  ResultPath: null , so execution output is unchanged. A Choice state guards it, because a missing JSONPath in  Parameters  raises  States.Runtime , which no Catch can handle.

3. Overcoming friction

Cold starts on a 1.18 GB image.
  • The first run: the first execution after the initial deploy took 81.2 s. Four of five invocations logged  INIT_REPORT … Status: timeout  at the 10-second init limit. Lambda then re-ran initialisation inside the invoke phase, so nothing failed, but the latency was real.
  • After that: once the image was cached, cold starts fell to 1.0–3.3 s.
  • A related fix: the Python 3.14 base image lacks  libexpat , which rasterio's bundled GDAL needs, so  dnf install expat  went into the Dockerfile.
The mitigations are operational:
  • run a warm-up execution after any image change;
  • keep infrastructure-only deploys from touching the image.  sam build  reproduced the identical digest, and every reviewed change set showed zero function rows.
The next step is lazy-loading the geospatial stack inside the handlers, to keep init under 10 s.
Lambda concurrency. The new account's Lambda concurrency limit was 10, so reserved concurrency was impossible. The Map's  MaxConcurrency  of 8 keeps the fan-out under it.
The CloudFront rollout that never was. The plan was a private S3 bucket behind CloudFront with origin access control.
  • The change set looked correct: four additions and one bucket-policy modification.
  • At execution:  CREATE_FAILED  with "Your account must be verified before you can add new CloudFront resources."
  • The rollback was clean ( UPDATE_ROLLBACK_COMPLETE ). The only debris was a site bucket with a  Retain  policy, which I deleted by hand.
I pivoted the same day. A small zip-packaged Lambda behind a function URL now serves the page.
  • What it serves: the site bucket, plus three published prefixes from the results bucket.
  • Hardening: GET and HEAD only, normalised paths, and security headers set in code.
  • What it didn't touch: the container image.
The lesson: a change set validates your template, not your account's eligibility. On new accounts, check service-level verification before designing around a service.

4. Cost and performance

I benchmarked 60 back-to-back executions after two manual runs. All 62 succeeded.
Metric
Value
Warm end-to-end median
7.1 s (95% CI 7.0–7.2 s)
Warm p95 / max
8.0 s / 8.2 s
 measure  warm median (per lake-month)
5.55 s
 route  /  report  warm median
0.473 s / 0.179 s
Lambda usage (60 runs)
2,094 GB-s, 300 requests
Cost per execution (on-demand)
$0.00076
That is $0.0455 for the whole batch, inside the free tier.  measure  peaks at 405 MB of its 3,008 MB; the extra memory buys CPU share, and right-sizing it is next. Warm runs re-read identical windows that GDAL can cache, so first reads of new scenes should run closer to 10–12 s per lake-month.
For auditability, the raw execution histories, Lambda  REPORT  lines, CloudTrail  StartExecution  records and metrics are published (redacted) in the repository. CI rebuilds the benchmark tables from them on every push. One finding there: CloudWatch's  ExecutionsSucceeded  reported 61 for 62 successful executions. For evidence, trust execution records over aggregate metrics.

Results and limits

Against the 2023 event:
  • Lake area: within 1.8% of ISRO's figure.
  • Flow path: within 1.9% of the published 67.5 km.
  • Forecast replay: a pre-registered replay using only the 14 September measurement forecast an arrival window of 101–183 minutes. The real flood took 138.
  • Limits: arrival timing is calibrated, and the modelled peak runs 1.4–2× above published reconstructions. Treat it as screening, not design.
Code and evidence:  github.com/suryaprasanthcse/CASCADE-GLOF-Telemetry  · Demo:  YouTube  · Live dossier:  CASCADE page 
Built solo during WeMakeDevs × AWS Environmental Hacks, with Claude Code as the coding agent. Contains modified Copernicus Sentinel data [2023].
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article