Skip to content

Event-Driven Geospatial Processing Patterns

Modern spatial platforms are moving away from always-on compute clusters toward reactive, serverless architectures that instantiate resources only when a discrete event arrives. This reference covers the foundational pipeline stages, hard platform limits, runtime packaging strategies, IAM scoping, and observability patterns that cloud GIS engineers, Python backend developers, and platform architects need to build production-grade event-driven geospatial systems on AWS, GCP, and Azure.


Foundational Architecture Patterns

A production event-driven geospatial pipeline composes five discrete stages. Each stage is independently scalable, which means a spike in drone orthomosaic uploads cannot stall a lightweight topology validation running in the same account. The stage boundaries are also the only places where state is permitted to exist: inside a stage, the handler is a pure function of its event envelope and the objects that envelope points at. That property is what makes retries safe, and it is why a well-decomposed pipeline can be re-run over a month of archived events without producing one duplicate geometry.

The five stages of an event-driven geospatial pipelineFive stages joined left to right: an ingestion trigger on object storage, metadata extraction of CRS, bounding box and format from the file header, a queue and orchestration layer, a compute function performing the spatial transformation, and an output and catalog stage writing cloud-optimized formats.IngestiontriggerS3 / GCS / BlobeventMetadataextractionCRS, bbox, formatheader read onlyQueue /orchestrationSQS or Pub/SubStep FunctionsCompute functionreproject, clip,tile, joinOutput / catalogCOG, FlatGeobuf,PMTiles + STACEvery arrow is an at-least-once, unordered delivery — the idempotency key travels in the envelope, not in the handler.
State exists only at the boundaries between stages. Inside a stage the handler is a pure function of its event envelope and the objects that envelope points at — which is what makes a retry safe.

Stage 1 — Ingestion Trigger. Cloud object storage is the most common entry point. When a user, drone autopilot, or automated satellite downlink deposits a spatial file, S3, GCS, or Azure Blob Storage emits a metadata event containing the bucket name, object key, content type, and file size. S3 and GCS Event Triggers for Shapefiles documents the multi-file aggregation pattern required for shapefiles: because .shp, .shx, .dbf, and .prj arrive as separate PUT events, a staging prefix must collect all components before a composite event is emitted downstream. Two delivery details bite here. A multipart upload — how any client uploads a 5 GB orthomosaic — emits one s3:ObjectCreated:CompleteMultipartUpload event at the end rather than one per part, so a trigger filtered only on s3:ObjectCreated:Put silently ignores every large file. And notifications are at-least-once and unordered, so the .dbf can arrive before the .shp and the same key can arrive twice.

Stage 2 — Metadata Extraction. A lightweight function reads the file header — without loading the full dataset — to extract coordinate reference system (CRS), bounding box, geometry type, feature count, and format. This metadata populates the event envelope and determines the routing path. Malformed files (missing .prj, unrecognized EPSG code, zero-byte uploads) are rejected here and sent to a dead-letter channel rather than consuming compute in later stages.

Doing this cheaply depends on GDAL’s virtual filesystem layer behaving. Opening /vsis3/bucket/scene.tif with default settings issues a directory listing before the first byte of the header is read, which on a prefix holding tens of thousands of tiles turns a 16 KB header read into a multi-second LIST. Set GDAL_DISABLE_READDIR_ON_OPEN=EMPTY_DIR, restrict CPL_VSIL_CURL_ALLOWED_EXTENSIONS to the formats the stage opens, and cap GDAL_CACHEMAX explicitly — it defaults to a share of host RAM, so a 512 MB metadata function can inherit a block cache sized for a 10,240 MB host and die on its first overview read.

Stage 3 — Queue / Orchestration. Inserting a message queue between ingestion and compute is the single most impactful reliability decision in an event-driven GIS pipeline. SQS and Pub/Sub Queue Routing Strategies covers fan-out architectures where a single validated event triggers parallel consumers — for example, a newly ingested LiDAR point cloud simultaneously driving DTM generation, building footprint extraction, and preview tileset publication. For jobs that exceed function time limits or require human review steps, Step Functions, GCP Workflows, or Azure Durable Functions replace simple queues with a full DAG orchestrator.

Three queue settings decide whether the stage is reliable. SQS caps a message at 256 KB, so send the object URI and the bounding box, never the features themselves. The visibility timeout must be at least the consuming function’s timeout — AWS recommends six times it — because a 15-minute tiling function behind a 30-second visibility timeout has its message redelivered to a second consumer roughly twenty-nine times while the first is still working, which is how one scene becomes a concurrency incident. And where per-asset ordering matters, an SQS FIFO queue keyed on the asset ID preserves it at 300 API calls per second, or 3,000 messages per second with ten-message batching.

Stage 4 — Compute Function. The function executes the spatial transformation: reprojection, clipping, rasterization, spatial join, or inference. Functions must be stateless and fully idempotent. Spatial state — CRS metadata, intermediate geometries, chunked raster tiles — must be externalized to object storage or a spatial database; storing it in /tmp between retries causes silent data loss. Ephemeral storage limits in AWS Lambda can exhaust /tmp before GDAL registers its first driver on large raster workloads, so chunk sizes must be calibrated to the platform ceiling.

The subtler failure is the opposite one: /tmp is not cleared between invocations on a warm execution environment. A function that unpacks a shapefile into /tmp/work/ and never deletes it will eventually read a stale .dbf from a previous job or exhaust the default 512 MB — while passing every cold-start test. Write intermediates under a directory named for the correlation ID, remove it in a finally block, and assert free space with os.statvfs("/tmp") at handler entry.

Stage 5 — Output / Catalog. Results are written as cloud-optimized formats — Cloud-Optimized GeoTIFF (COG), FlatGeobuf, or PMTiles — and registered in a STAC catalog or tile index. Conditional writes (ETags, DynamoDB ConditionExpression) prevent duplicate inserts when upstream retries resubmit the same event. S3 has offered strong read-after-write consistency for GET, PUT, and LIST since December 2020, so the catalog function can list the output prefix the moment tiling finishes and trust the result — but that guarantee covers the object store only. The catalog record and the object are two separate writes, and a crash between them leaves an orphan, so write the object first and the catalog entry second.

Event Delivery Semantics You Have to Design Around

Every provider’s notification path is at-least-once and none guarantees ordering across keys, which produces three requirements. Idempotency keys must be derived from the input URI plus the processing parameters, never from a timestamp or a handler-generated UUID, or a retry writes a second output under a second key. Retry budgets must be explicit: an asynchronously invoked Lambda retries twice by default, at roughly one- and two-minute intervals, and discards the event after a maximum event age of six hours unless an on-failure destination or DLQ is attached. And poison messages must be separable from transient ones — a malformed .prj fails identically on all three attempts, whereas a throttled S3 read succeeds on the second — so classify the exception and fail fast on the former instead of burning the budget.


Platform Constraints Reference Table

Every platform imposes hard limits that directly constrain what a single function invocation can accomplish on spatial data. Engineers must design chunking strategies, memory allocations, and timeout budgets within these ceilings.

Serverless platform ceilings for geospatial workloadsComparison grid of five hard limits — maximum execution timeout, memory ceiling, ephemeral tmp storage, deployment package size and concurrency quota — for AWS Lambda, GCP Cloud Functions 2nd generation, and Azure Functions on the Consumption plan.The ceilings a spatial fan-out reaches firstAWS LambdaGCP Functions (2nd gen)Azure (Consumption)Max execution timeout15 min60 min9 min via Eventarc10 min5 min defaultMemory ceiling10,240 MB32,768 MB1,536 MBEphemeral /tmp10,240 MB512 MB defaulttmpfscounts against memory~500 MBper instanceDeployment package250 MB unzipped10 GB as a container100 MB compressedsource upload1 GB zipapp content shareConcurrency quota1,000regional, soft3,000per region200per functionConcurrency is a rate as well as a ceiling: a Lambda function adds 1,000 concurrent executions every 10 seconds, so an 8,000-tile burstthrottles before it scales.
Azure Consumption binds first on every axis, and its 1,536 MB memory ceiling is what forces windowed reads on a 500 MB GeoTIFF that AWS or GCP would open whole.
Constraint AWS Lambda GCP Cloud Functions 2nd gen Azure Functions (Consumption)
Max execution timeout 15 min 60 min 10 min
Memory ceiling 10,240 MB 32,768 MB 1,536 MB
Ephemeral disk (/tmp) 10,240 MB (512 MB default) tmpfs — counts against memory ~500 MB
Max deployment package 250 MB unzipped / 10 GB container 100 MB compressed source 1 GB zip
Reserved concurrency quota 1,000 per region (soft, adjustable) 3,000 per region (default) 200 per function (default)
Geospatial impact 15 min cap blocks full-scene Sentinel-2 reprojection without chunking 60 min and 32,768 MB support moderate whole-scene processing 1,536 MB ceiling forces aggressive GDAL driver stripping and windowed reads

The Azure Consumption plan’s 1,536 MB memory limit is the most restrictive. Loading a full GDAL rasterio dataset for a 500 MB GeoTIFF is not feasible without windowed reads. Memory and CPU Allocation for Raster Workloads provides per-platform tuning guidance and benchmarks showing that doubling Lambda memory from 3 GB to 6 GB frequently halves wall-clock time on CPU-bound vector operations, reducing overall cost despite higher per-millisecond pricing.

Each of these numbers carries a footnote that matters more than the number. On AWS, memory is allocated in 1 MB steps from 128 MB to 10,240 MB and vCPU share is a linear function of it: one full vCPU arrives at 1,769 MB and the 10,240 MB ceiling buys roughly six, which is why an under-provisioned raster function usually fails with a timeout rather than an out-of-memory kill. The 10,240 MB /tmp allocation is opt-in — the default is 512 MB and the remainder is billed per GB-second above that floor. On GCP, the 60-minute ceiling applies to HTTP-triggered 2nd-gen functions; the same function invoked through Eventarc is capped at 9 minutes (540 s), and its /tmp is a tmpfs, so a 12 GB intermediate GeoTIFF does not spill to disk — it evicts the raster block cache out of the same 32,768 MB. On Azure Consumption, the 10-minute maximum is not the default: functionTimeout ships at 5 minutes and must be raised in host.json.

Concurrency behaves differently from the other four because it is a rate as well as a ceiling. A Lambda function scales by 1,000 concurrent executions every 10 seconds until it reaches the account’s regional limit, so a fan-out that dispatches 8,000 tile jobs at once does not fail — it throttles for the first seventy seconds, and every throttled invocation is a retry that lands back on the queue. Reserving concurrency for the tile processor and setting the orchestrator’s map concurrency to that reserved figure turns a retry storm into flow control.


Core Geospatial Processing Patterns

Object Storage Triggers and Multi-File Format Handling

The file-based trigger pattern works cleanly for single-file formats — GeoPackage (.gpkg), FlatGeobuf (.fgb), and COG — because a single PUT event maps to a complete, processable dataset. Shapefiles break this assumption. A reliable shapefile ingestion pattern requires:

  1. All components land in a staging prefix (e.g., s3://bucket/staging/upload-id/).
  2. A coordinator function checks for the presence of .shp, .shx, .dbf, and .prj before emitting a composite processing event.
  3. Only after the composite event is confirmed does the pipeline proceed to metadata extraction.

Triggering GCP Cloud Functions on New Shapefile Uploads walks through the GCS-specific implementation using Pub/Sub filtering and a Cloud Firestore completeness tracker.

The completeness check needs a deadline as well as a condition. A .prj that never arrives is indistinguishable from one that is thirty seconds late, so record each component’s arrival against the upload ID with a TTL — 15 minutes is generous for a browser upload — and emit an explicit incomplete-upload rejection when the timer fires rather than leaving the prefix in limbo. Two optional components still need a decision: a missing .cpg leaves GDAL guessing between UTF-8 and Latin-1 on every accented place name, and a missing .shx is recoverable only with SHAPE_RESTORE_SHX=YES. Record which assumption the pipeline made in the event envelope, so a downstream encoding complaint traces back to the ingestion decision that caused it.

Message Queue Routing and Dead-Letter Handling

Direct function-to-function invocation creates a failure cascade: if the downstream transformation function throws, the ingestion function also fails and retries, potentially reprocessing already-written data. A message queue absorbs this by decoupling invocation from consumption.

Implementing Dead-Letter Queues for Failed Vector Jobs covers the DLQ configuration for spatial jobs, including the recommended maxReceiveCount thresholds (3–5 for parsing errors, 1 for OOM conditions) and a structured redrive policy that logs the failing feature payload and CRS metadata for forensic inspection.

The gap between those two thresholds is the whole point. A parsing error is often transient at the batch level — a partial read, a throttled range request — and three attempts resolve it. An out-of-memory kill is deterministic: the same 2-million-vertex multipolygon terminates the same 3,008 MB function every time, and each retry burns a full timeout of billed duration first. Partial batch responses matter as much: when a Lambda consumes ten SQS messages and the seventh throws, the default behaviour returns all ten and reprocesses the six that already succeeded. Enabling ReportBatchItemFailures narrows redelivery to the records that actually failed — for a batch of tile jobs, the difference between reprocessing one tile and reprocessing the scene.

Batch vs Stream for Spatial Workloads

Historical dataset migrations, nightly satellite ingestion, and compliance reporting tolerate batch execution — loading is cheaper and parallelism is simpler to control. Real-time asset tracking, flood sensor networks, and live traffic routing require sub-second latency and continuous geometry streams.

Understanding the trade-offs in Batch vs Stream Geospatial Processing is critical for both infrastructure sizing and state management. Stream processing frameworks use micro-batching and windowing functions to handle continuous geometry streams and maintain lightweight spatial join state. When to Use Batch vs Streaming for Real-Time AIS Tracking provides a concrete decision framework for maritime vessel position pipelines, where update frequency, geometry complexity, and the need for trajectory smoothing determine the right model.

Chunked I/O for Large Raster and Satellite Imagery

A single 10 GB Sentinel-2 scene or a 50 GB orthomosaic cannot fit within a Lambda or Cloud Function’s memory ceiling. The solution is to process spatial data in window-aligned chunks, reading and writing only the required pixel extents using HTTP range requests against COG files.

Chunked I/O for Large Satellite Imagery documents the tiled read pattern using rasterio.windows.Window and the GDAL VSI layer to stream range requests without materializing the full dataset. Optimizing Chunked I/O for Multi-Band Sentinel-2 Processing extends this to multi-band interleaving strategies that minimize HTTP round-trips when compositing RGB+NIR stacks. This pattern pairs naturally with STAC catalogs, which allow functions to discover and fetch only the relevant band assets without enumerating full archive prefixes.

Chunk geometry is not a free choice. A read window that is not aligned to the file’s internal block layout forces GDAL to fetch every block the window touches and discard the margins, so a 500×500 window over a 512×512-blocked COG transfers four blocks per tile instead of one and quadruples both latency and egress. Align windows to the block size reported by rio info --tell-me-more, and size them so window_pixels × bands × dtype_bytes — 512 × 512 × 4 bands × 2 bytes is 2 MB — stays under about 60% of allocated memory. The remainder goes to the output buffer, compression scratch space, and driver state that exists before your first line runs.

Block-aligned versus straddling read windows on a Cloud-Optimized GeoTIFFA grid of internal 512 by 512 pixel blocks in a Cloud-Optimized GeoTIFF. One block is highlighted to show an aligned read window that fetches a single block, and a two-by-two group of blocks is highlighted to show a 500 by 500 window that straddles boundaries and forces four block fetches.Read windows must land on the COG's own block grid1 read512×512 internal blocks512 × 512 × 4 bands × 2 bytes is 2 MBof pixel data per aligned tile — keep itunder 60% of allocated memory.aligned — 1 blockstraddling — 4 blocksRead the block layout once in the orchestrator and pass it in the event envelope; at 40,000 tiles that removes 40,000 header round-trips.
Both windows ask for about a quarter of a megapixel. The misaligned one transfers four blocks and discards the margins — four times the latency and four times the egress, per tile, across every tile in the fan-out.

The fan-out has its own arithmetic. One Sentinel-2 band at 10 m resolution splits into roughly 470 tiles of 512×512, so a mosaic run over a national footprint dispatches tens of thousands of invocations that all begin with the same COG header read. Have the orchestrator read the header once and pass the block layout and CRS in the event envelope; at 40,000 tiles that removes 40,000 round-trips against a single object.


Runtime Optimization for Geospatial Libraries

GDAL, PROJ, rasterio, Shapely, and Fiona carry significant binary weight. Unoptimized Lambda deployment packages can reach 500 MB, and cold start mapping for Python GDAL shows that shared-library resolution during initialization can add 8–14 seconds of latency before the first byte of spatial data is read.

Packaging strategies:

  • Strip unused GDAL drivers. Build GDAL from source with --with-formats limited to the drivers your pipeline actually reads (e.g., GTiff,GPKG,FlatGeobuf). This reduces the GDAL binary by 40–60 MB.
  • Set environment variables explicitly. Never rely on default path resolution at runtime:
python
import os
os.environ["GDAL_DATA"]        = "/opt/share/gdal"
os.environ["PROJ_LIB"]         = "/opt/share/proj"
os.environ["LD_LIBRARY_PATH"]  = "/opt/lib:" + os.environ.get("LD_LIBRARY_PATH", "")
  • Respect the layer arithmetic. A function may attach at most five layers, and the function package plus every layer must stay under 250 MB unzipped combined. Splitting a 240 MB GDAL stack across three layers buys no headroom — it is the same 240 MB against the same ceiling; what layering wins is that binaries are cached and versioned independently of application code. PROJ’s optional datum grids are the usual straw that breaks it: ship only the grids your transformations need, and pin the transformation pipeline explicitly so a missing grid fails loudly instead of degrading silently to a ballpark shift.
  • Use container images for heavy dependencies. Lambda container images support up to 10 GB, eliminating the 250 MB unzipped limit. Build on the official public.ecr.aws/lambda/python:3.12 base image using the same Amazon Linux 2023 environment as the Lambda runtime to avoid libc symbol mismatches.
  • Provisioned concurrency for latency-sensitive endpoints. Reducing Python GDAL Cold Starts with Provisioned Concurrency demonstrates that pre-warming 5–10 instances of a GDAL-heavy function brings p99 cold-start latency below 200 ms.
  • GIL / multiprocessing trade-offs. Python’s GIL prevents true thread-level parallelism for CPU-bound GDAL operations. Prefer multiprocessing.Pool for tile parallelism within a single function, and size the pool to (memory_mb / per_tile_footprint_mb) to avoid OOM. rasterio releases the GIL during C-level reads, so threaded I/O with a thread pool executor is safe for download-heavy pipelines.

Python Layer Management and Size Reduction and Stripping Unnecessary Python Packages from AWS Lambda Layers cover the pip install --no-compile and pyc removal approaches that consistently shave 30–80 MB from geospatial Lambda layers. Building Minimal Docker Images with Alpine and GDAL extends this to container-based deployments where multi-stage builds drop final image size below 200 MB.


Security, IAM, and Data Governance

IAM Security Boundaries for Cloud GIS scopes each pipeline stage to the minimum required S3 prefix. The same principle applies across all three providers.

Least-privilege role scoping per pipeline stage:

  • Ingestion function: s3:GetObject on staging/* only, sqs:SendMessage on the ingestion queue ARN. No write access to the processed bucket.
  • Metadata extraction function: s3:GetObject on staging/*, dynamodb:PutItem on the event log table (with a condition expression to prevent overwrites of completed jobs).
  • Transform function: s3:GetObject on staging/*, s3:PutObject on processed/*, kms:Decrypt and kms:GenerateDataKey on the output bucket CMK.
  • Catalog publish function: s3:PutObject on catalog/*, dynamodb:UpdateItem on the STAC item table.

VPC endpoints and data residency. Spatial data must never traverse the public internet between pipeline stages. Configure VPC Gateway Endpoints for S3 and DynamoDB, and Interface Endpoints for SQS, KMS, and Step Functions. For regulated industries, route events to region-specific queues and storage classes with object-level CloudTrail logging to maintain an auditable data lineage chain.

Encryption. Enforce SSE-KMS on all staging and processed buckets. Pass the KMS key ARN as an explicit environment variable — never hard-code it:

python
import os
KMS_KEY_ARN = os.environ["OUTPUT_KMS_KEY_ARN"]

Apply customer-managed keys (CMK) rather than AWS-managed keys so you retain the ability to disable key rotation and audit decrypt calls in CloudTrail.

Where least privilege leaks. Two patterns undo the scoping above. The first is the shared scratch prefix: granting every stage s3:PutObject on scratch/* lets the tiling function overwrite the metadata function’s output, so scope it to scratch/${correlation_id}/* with a policy variable instead of a wildcard. The second is presigned upload URLs, which inherit the signer’s permissions for their whole validity window — sign them with a dedicated upload-only role scoped to the staging prefix, keep the expiry short, and pin the content-length range so a 40 GB upload cannot be pushed through a URL issued for a 20 MB GeoPackage.


Observability, Cost Control, and Fallback Patterns

Structured logging with spatial context. Inject a correlation ID into every event payload at ingestion and propagate it through queue message attributes and function log entries. Log spatial metrics in every handler:

python
import json, logging, time
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def handler(event, context):
    start = time.perf_counter()
    # ... processing ...
    logger.info(json.dumps({
        "correlation_id": event["correlationId"],
        "feature_count":  result["count"],
        "crs":            result["crs"],
        "bbox":           result["bbox"],
        "duration_ms":    round((time.perf_counter() - start) * 1000, 1),
        "tiles_written":  result["tile_count"],
    }))

This structured payload enables CloudWatch Insights queries, Cloud Monitoring log-based metrics, and Azure Monitor KQL to compute cost-per-tile and cost-per-feature ratios as pipelines scale.

Distributed tracing. Use OpenTelemetry with the OTLP exporter to propagate trace context across Lambda invocations and SQS hops. Instrument the GDAL VSI read path with spans so you can distinguish HTTP range-request latency from compute time in your trace waterfall.

Circuit-breaker patterns for OOM and timeout fallback. When a spatial function exceeds its memory ceiling on an unexpectedly large input (e.g., a 2M-vertex polygon from a poorly generalized administrative boundary dataset), the default Lambda behavior is a silent OOM termination that the queue treats as a retriable failure — causing the same oversized payload to be retried until the DLQ threshold is reached. Break this loop with a pre-flight geometry complexity check:

python
from shapely.wkb import loads as wkb_loads

MAX_VERTEX_COUNT = 500_000

def preflight_geometry(wkb_bytes: bytes) -> None:
    geom = wkb_loads(wkb_bytes)
    count = sum(len(c.coords) for c in geom.geoms) if geom.geom_type.startswith("Multi") else len(geom.exterior.coords)
    if count > MAX_VERTEX_COUNT:
        raise ValueError(f"Geometry exceeds vertex budget ({count} > {MAX_VERTEX_COUNT}); route to heavy-compute queue")

Pair this with a second SQS queue bound to a higher-memory function for oversized geometries, so the main pipeline is never blocked.

Routing a spatial payload by vertex count before processingA decision on the incoming geometry's vertex count with three outcomes: under 500,000 vertices goes to the main tile queue on a 3,008 MB function with maxReceiveCount 3, over 500,000 goes to a heavy-geometry queue on a 10,240 MB function with maxReceiveCount 1, and unparseable WKB goes straight to the dead-letter queue with no retry.Pre-flight vertex count, before any prepared geometry is builtVertex count above 500,000 on theincoming geometry?noMain tile queue3,008 MB functionmaxReceiveCount 3yesHeavy-geometry queue10,240 MB functionmaxReceiveCount 1unparseable WKBDead-letter queueno retry attemptedoperator review
An out-of-memory kill is deterministic, so retrying it costs a full timeout of billed duration and changes nothing. Counting coordinates first is linear and allocation-free — it is the cheapest check in the pipeline.

The pre-flight check works because it is cheap relative to what it prevents: counting coordinates on a WKB payload is linear and allocation-free next to building a prepared geometry and intersecting against it. Derive the threshold rather than guessing — a GEOS prepared geometry costs roughly 100–200 bytes per vertex once its STRtree index is built, so divide the memory allocation by that footprint and take 60%. Emit the count as a metric on every invocation, not only on rejections; the distribution tells you whether the threshold is doing useful work or quietly diverting a tenth of production traffic.

Detecting silent failure. The dangerous failures in spatial pipelines return HTTP 200. A reprojection that falls back to a null datum transform shifts coordinates by up to a few hundred metres without raising, and a clip against a bounding box in the wrong CRS produces an empty output that the catalog dutifully registers. Guard both with assertions the pipeline can fail: the output bbox must intersect the reprojected input bbox, feature count must stay within an expected ratio of the input, and the written CRS must match the authority code requested. Treat a zero-feature output as an error unless the job explicitly declares that an empty result is valid.

Cost-per-feature monitoring. Tag every Lambda invocation with the spatial_job_type dimension and publish a custom CloudWatch metric for features processed per invocation. Plot cost-per-feature weekly; a rising trend signals either input data quality degradation (more complex geometries, larger files) or a regression in chunking efficiency.


Operational Checklist

Use this checklist before promoting an event-driven geospatial pipeline to production:


Frequently Asked Questions

When should I use streaming instead of batch for geospatial data?

Use streaming for sub-second latency requirements such as live AIS vessel tracking, flood sensor networks, or real-time traffic routing. Use batch for nightly satellite ingestion, compliance reporting, or workloads where per-record overhead would make streaming uneconomical. The Batch vs Stream Geospatial Processing cluster provides decision criteria and cost comparisons.

How do I avoid duplicate geometry inserts from retried Lambda events?

Hash the input object URI and embed the hash as a conditional write expression before inserting. On AWS, use ConditionExpression="attribute_not_exists(correlation_id)" on DynamoDB. On GCP, use an ifGenerationMatch: 0 precondition on GCS. On Azure, use an ETag check on Blob Storage. Every function handler must be fully idempotent.

What is the safest format for serverless shapefile ingestion?

Stage all shapefile components (.shp, .shx, .dbf, .prj) in a dedicated prefix, verify completeness before triggering downstream functions, and then convert to GeoPackage or FlatGeobuf for single-file reliability in subsequent pipeline stages. For raster uploads, Cloud-Optimized GeoTIFF is the preferred target format.

How do I keep GDAL cold starts below 2 seconds?

Strip unused GDAL drivers at build time, set GDAL_DATA and PROJ_LIB explicitly, use a container image to avoid the 250 MB zip decompression overhead, and enable provisioned concurrency for latency-sensitive endpoints. The Cold Start Mapping for Python GDAL cluster provides a systematic profiling sequence.

What concurrency limit should I set on my geospatial Lambda?

Start with a reserved concurrency of 50–100 per function and load-test at 5–10× expected peak. Monitor the ConcurrentExecutions CloudWatch metric and the DLQ depth together — if the DLQ grows under load, raise concurrency; if costs spike without DLQ growth, implement a back-pressure mechanism in the queue consumer. Remember that the default 1,000 regional limit is shared by every function in the account and region, so reserving 400 for a tile processor removes 400 from everything else, including the ingestion trigger that feeds it.

Why does my raster function time out instead of running out of memory?

Because vCPU share is tied to memory allocation. On AWS Lambda, one full vCPU arrives at 1,769 MB and the 10,240 MB ceiling provides roughly six; a function pinned at 1,024 MB is running on well under a single core, so a CPU-bound resample that would finish in 90 seconds at 3,008 MB grinds past the 15-minute ceiling instead. Before adding chunking, raise memory one step and compare wall-clock time — if duration falls roughly in proportion, the job was CPU-starved, not memory-starved, and the higher allocation is usually cheaper overall because you are billed for GB-milliseconds.

What should the metadata function do when a dataset has no .prj file?

Reject it into a quarantine prefix rather than assuming EPSG:4326. An unlabelled shapefile is as likely to be in a national grid as in geographic coordinates, and a wrong assumption produces geometrically plausible output that nothing downstream will flag. Record the rejection with the file’s bounding box in native units — a bbox with values in the hundreds of thousands is almost certainly projected — so an operator can assign the CRS once and replay the event rather than re-uploading the dataset.


Topics in this section