Choosing Instance Types for Raster Workloads

Compare instances by cost per tile, not by price per hour:

def cost_per_tile(price_per_hour: float, tiles: int, seconds: float) -> float:
    return price_per_hour * (seconds / 3600) / tiles

print(cost_per_tile(0.68, 400, 1520))   # c7g.4xlarge example
print(cost_per_tile(1.01, 400, 1210))   # m7i.4xlarge example

The cheapest instance per hour is rarely the cheapest per result. Raster work is usually bound by memory or network before CPU, and the right machine is the one whose bottleneck matches the workload’s. This page belongs to optimizing pipeline cost and performance in Cloud Execution & Orchestration.


Three Resources, Three Families

Match the bottleneck to the family Workloads with modest memory per task and heavy arithmetic suit compute-optimised families with about 2 GB per vCPU. Typical read-reduce-write pipelines suit general-purpose families with about 4 GB per vCPU. Datacube reductions over long time series, large reprojections and model inference on big tiles suit memory-optimised families with about 8 GB per vCPU. Network bandwidth scales with instance size in every family. GB of memory per vCPU decides the family compute (c) ~2 GB / vCPU index math on small tiles COG encoding general (m) ~4 GB / vCPU read–reduce–write most tile pipelines memory (r) ~8 GB / vCPU long time-series cubes large reprojections Measure peak memory per task first; the ratio to cores picks the column.

The deciding measurement is peak memory per concurrent task. If a task peaks at 3 GB and each core runs one task, compute-optimised machines with 2 GB per core will spill or be killed, and paying for fewer, bigger tasks per machine wastes the cores you bought. Profiling one representative tile, as in profiling memory in a raster worker, gives this number before any money is spent.


Environment & Setup

Package Version pin Used for
boto3 >=1.34 Price and instance metadata
dask[distributed] >=2023.1 The workload under test
pandas >=2.0 Comparing benchmark results
pip install "boto3>=1.34" "dask[distributed]>=2023.1" "pandas>=2.0"

Complete Working Example

import pandas as pd

# Results from running the same 400-tile benchmark on each candidate
runs = pd.DataFrame([
    # type,         vcpu, mem_gb, net_gbps, usd_h, seconds, spilled_gb
    ("c7g.4xlarge",   16,  32, 15.0, 0.58, 1580, 11.2),
    ("m7g.4xlarge",   16,  64, 15.0, 0.65, 1330,  0.0),
    ("r7g.4xlarge",   16, 128, 15.0, 0.86, 1320,  0.0),
    ("m7i.4xlarge",   16,  64, 12.5, 0.81, 1250,  0.0),
    ("m7g.8xlarge",   32, 128, 15.0, 1.31,  690,  0.0),
], columns=["type", "vcpu", "mem_gb", "net_gbps", "usd_h", "seconds", "spilled_gb"])

TILES = 400
runs["usd_per_tile"] = runs.usd_h * runs.seconds / 3600 / TILES
runs["tiles_per_h"] = TILES / (runs.seconds / 3600)
runs["gb_per_vcpu"] = runs.mem_gb / runs.vcpu
print(runs.sort_values("usd_per_tile")[["type", "gb_per_vcpu", "usd_per_tile", "tiles_per_h", "spilled_gb"]])

The benchmark shows the typical pattern. The compute-optimised machine is cheapest per hour but spills to disk, so it is slower and not cheapest per tile. The memory-optimised machine does not spill but is paying for memory the workload does not use. The general-purpose ARM machine wins on cost per tile, and the larger size of the same family is nearly twice as fast for twice the price — a sign the workload scales cleanly and that the choice between sizes can be made on deadline rather than cost.


Network Is the Hidden Resource

Bandwidth scales with instance size Sustained read throughput from object storage rises with instance size, because cloud providers allocate network bandwidth proportionally. Small instances reach their network limit before their CPU is busy on read-heavy COG workloads, so a few large instances often outperform many small ones with the same total cores. Sustained S3 read throughput by instance size large xlarge 2xlarge 4xlarge 8xlarge Baseline bandwidth, not "up to" burst bandwidth, is what long jobs get.

Instance listings quote network bandwidth “up to” some figure for smaller sizes, meaning a burst allowance that runs out after minutes. A pipeline reading COGs for hours runs at the baseline, which on the smallest sizes can be a fraction of a gigabit. On read-heavy workloads, a fleet of small instances with the same total cores as a few large ones is then network-starved. Measuring actual throughput, as in benchmarking COG read throughput from object storage, on the instance size you plan to use avoids the surprise.


ARM, Spot and Regions

ARM-based instances such as AWS Graviton are typically 15–25% cheaper for similar performance, and the geospatial stack — GDAL, rasterio, NumPy, Dask — has mature ARM wheels and conda packages. The main cost is building multi-architecture container images, covered in building a slim GDAL Docker image. Spot capacity cuts compute cost by 60–70% and suits tile pipelines well, since each tile is independent and can be retried; diversify across several instance types of the same shape so a capacity shortage in one does not stall the run. Finally, run compute in the same region as the data — cross-region reads add egress charges that can exceed the instance cost itself.


Local Disk and Scratch Space

Some raster steps need fast local storage: Dask spilling to disk, GDAL writing a temporary GeoTIFF before converting to COG, or warping large mosaics with an on-disk cache. Instance families with a d suffix carry local NVMe drives that are far faster than network-attached volumes, and for spill-heavy or write-heavy jobs they can shorten runtime enough to pay for themselves. For pipelines that stream reads from object storage and write results directly back, local disk is irrelevant and a small root volume is enough. Check where temporary files actually land — TMPDIR, CPL_TMPDIR for GDAL, and Dask’s temporary-directory — and point them at the fast disk when one is present.

Revisit the Choice

Instance generations change every year or two, usually with better price-performance, and a pipeline’s workload drifts as features are added. Re-run the benchmark whenever the pipeline changes substantially or a new generation appears; it takes an hour and often saves a noticeable share of the monthly bill. Keep the benchmark script and its tile list in the repository so the comparison is repeatable and genuinely like for like across generations.


Verification

Signs the machine fits the work A well-matched instance shows no memory spill to disk, CPU utilisation consistently above about seventy percent, and network throughput below its baseline limit. Spill means too little memory per core; low CPU with saturated network means the instance is too small for its reads. Three signals from a production-sized run spill should be zero CPU > 70% sustained network below baseline cap Two of three healthy usually means one size up or down in the same family.
best = runs.sort_values("usd_per_tile").iloc[0]
assert best.spilled_gb == 0, "cheapest option spills; memory per core is too low"
print(f"choose {best.type}: ${best.usd_per_tile * 1000:.2f} per 1,000 tiles")

Common Errors

The cheapest instance per hour costs the most per run

It spills or is network-starved. Compare cost per tile from a real benchmark.

Performance collapses after the first few minutes

Burst network credits ran out. Size for baseline bandwidth or use larger instances.

Container fails to start on ARM instances

The image was built for x86 only. Build a multi-architecture image.

Spot runs stall for hours

Only one instance type was allowed. Diversify across similar types and sizes.


Frequently Asked Questions

Q: Which instance family suits raster processing? General-purpose families with about 4 GB per vCPU suit most tile pipelines. Use compute-optimised only when memory per task is small, and memory-optimised for long time-series cubes or large reprojections.

Q: Are ARM instances suitable for GDAL work? Yes. GDAL, rasterio, NumPy and Dask all have mature ARM builds, and ARM instances are usually cheaper for similar throughput.

Q: Should I use many small instances or a few large ones? For read-heavy work, fewer larger instances, because baseline network bandwidth scales with size and small instances are throttled.

Q: Is spot capacity safe for raster pipelines? For independent, retryable tile jobs, yes. Checkpoint per tile, diversify instance types, and keep long single tasks on on-demand capacity.