staRburst makes it trivial to scale your parallel R code from your laptop to 100+ AWS workers. This vignette walks through setup and common usage patterns.
staRburst offers a few entry points. They share the same engine — pick the one that fits how your code is written:
| Need | Use |
|---|---|
| Map a function over inputs (simplest) | starburst_map() |
Existing future / furrr code |
plan(starburst) then future_map() |
| Long-running job that survives disconnects | starburst_session() |
| Explicit, reusable worker cluster | starburst_cluster() |
| Configure account/backend defaults | starburst_config() |
If you’re new, start with starburst_map() — the next
section walks through a first job with it. If you already have
future/furrr code, skip to Migrating existing future /
furrr code; the two styles are equivalent and shown side by side
there.
Every API above runs on one of two compute backends, chosen with
launch_type:
starburst_setup() provisions
its capacity provider for you, so it works out of the box.launch_type = "FARGATE"). Fully managed and needs
no starburst_setup_ec2 / capacity
provider, but has cold-start latency and is bounded by your account’s
Fargate vCPU quota. Reach for it if you specifically want task-based
serverless execution.You don’t have to choose up front — leave the default (EC2) and add
launch_type = "FARGATE" later if you want to compare.
Local R session
|
| (1) serialize your function + inputs + detected globals (qs2)
| AWS credentials are read HERE, from your local environment
v
S3 bucket + staRburst control plane (ECS/ECR)
|
| (2) launch workers: EC2 (default, Spot) or Fargate
| each worker's R packages come from your renv.lock, baked
| into a Docker image cached in ECR
v
Remote R workers (one task per input element)
|
| (3) each worker pulls a task from S3, runs it, writes the
| result + logs back to S3
v
Local R session <-- results collected in order (starburst_map / cluster)
or
Detached-session store in S3 <-- collected later via starburst_session_attach()
What to know from this picture:
renv.lock,
installed into the worker image once and cached in ECR (this is the
first-run build cost).session$cleanup() (or normal completion of
starburst_map()) tears down workers and removes the job’s
S3 objects.Before using staRburst, you need to configure AWS resources. This only needs to be done once.
This will: - Validate your AWS credentials - Create an S3 bucket for
data transfer - Create an ECR repository for Docker images - Set up an
ECS cluster and VPC resources - Provision the default EC2
capacity (launch template + Auto Scaling Group + capacity
provider for c7g.xlarge) so the default EC2 backend works
immediately — created at zero instances, so no compute cost until you
run a job (skip with setup_ec2 = FALSE if you only use
Fargate) - Check Fargate quotas and offer to request increases - Build
the initial worker image
Provisioning the AWS resources takes about 2
minutes. On the first run, staRburst also
builds the worker Docker image, which adds 5–10 minutes
(it is cached and reused afterwards, and you can skip it with
starburst_setup(build_image = FALSE) and build lazily on
first job launch).
The simplest way to use staRburst is starburst_map() —
hand it your inputs and a function, and it runs the function over each
input on AWS workers:
library(starburst)
# Define your work
expensive_simulation <- function(i) {
# Some computation that takes a few minutes
results <- replicate(1000, {
x <- rnorm(10000)
mean(x^2)
})
mean(results)
}
# Run across 50 cloud workers (EC2 by default)
results <- starburst_map(1:100, expensive_simulation, workers = 50)
#> [Starting] Starting starburst cluster with 50 workers
#> [Status] Processing 100 items with 50 workers
#> [Starting] Submitting 100 tasks...
#> [Wait] Progress: 100/100 (128.0s)
#> [OK] Completed in 128.0 seconds
#> [Cost] Estimated cost: $0.85That is the whole workflow: starburst_setup() once, then
starburst_map() per job. Everything below builds on
this.
future / furrr
codeIf you already use future or furrr, you
don’t need starburst_map() — set the plan to
starburst and your existing future_map() /
future_lapply() calls run on AWS unchanged. The two styles
are equivalent; use whichever matches your code:
library(furrr)
library(starburst)
# Local baseline
plan(sequential)
results_local <- future_map(1:100, expensive_simulation)
# Same code, now on 50 cloud workers — just change the plan
plan(starburst, workers = 50)
results_cloud <- future_map(1:100, expensive_simulation)
# Results are identical to the local run
identical(results_local, results_cloud)
#> [1] TRUEThe two calls below do the same thing — pick the one that fits your codebase:
Rather than repeat toy snippets here, the article gallery carries complete, measured end-to-end examples — each with real runtimes, costs, and the batching choices that make (or break) a workload:
Before you size a job, read the two guides that report real numbers on when the cloud wins, when it loses, and how to pick task/worker counts: Workload Shapes and Performance. The short version: each task should be real work (seconds or more), and thousands of tiny tasks should be batched into dozens–hundreds — see Batch Small Tasks below.
Base R’s read.csv()/readRDS() cannot read
s3:// URLs — you need an S3 client on the worker. A small
helper keeps the examples below concrete and copy-pasteable (the worker
image already includes paws.storage):
# Download an s3://bucket/key object to a temp file and read it on the worker.
read_s3 <- function(s3_uri, reader = readRDS) {
m <- regmatches(s3_uri, regexec("^s3://([^/]+)/(.+)$", s3_uri))[[1]]
bucket <- m[2]; key <- m[3]
tmp <- tempfile()
obj <- paws.storage::s3()$get_object(Bucket = bucket, Key = key)
writeBin(obj$Body, tmp)
reader(tmp)
}If your data is already in S3, workers can read it directly:
For smaller datasets, you can pass data as arguments:
# Load data locally
data <- read.csv("local_file.csv")
# staRburst automatically uploads to S3 and distributes
plan(starburst, workers = 50)
# `replicates` is your vector of inputs (e.g. bootstrap replicate ids)
results <- future_map(replicates, function(i) {
# Each worker gets a copy of 'data'
bootstrap_analysis(data, i)
})For very large objects, upload once to S3 yourself and have each worker read it from there, rather than serializing the object into every task:
# Upload once from your machine
paws.storage::s3()$put_object(
Bucket = "my-bucket", Key = "large_data.rds",
Body = "huge_file.rds"
)
s3_path <- "s3://my-bucket/large_data.rds"
# Workers read from S3 inside the task (read_s3 helper defined above)
plan(starburst, workers = 100)
# `tasks` is your vector of work items
results <- future_map(tasks, function(i) {
data <- read_s3(s3_path) # readRDS by default
process(data, i)
})# Set an hourly cost ceiling (USD/hour) — jobs above this rate won't start
starburst_config(
max_hourly_cost = 10, # Don't start jobs estimated over $10/hour
cost_alert_threshold = 5 # Warn at $5/hour
)
# Now jobs exceeding limit will error before starting
plan(starburst, workers = 1000) # Would cost ~$35/hour
#> Error: Estimated cost ($35/hr) exceeds limit ($10/hr)After a run, staRburst reports an estimated cost — the measured worker runtime multiplied by the current AWS price for your instance type (On-Demand or Spot), looked up live from the AWS Pricing API (and cached; it falls back to built-in rates offline). It is a close estimate, not a figure from AWS billing / Cost Explorer.
This section applies to the Fargate backend. On the default EC2 backend, worker count is bounded by your normal EC2 On-Demand / Spot instance limits, not the Fargate vCPU quota below. If you stay on EC2 you can usually skip this.
If you request more workers than your quota allows, staRburst automatically uses wave-based execution:
# Quota allows 25 workers, but you request 100
plan(starburst, workers = 100, cpu = 4)
#> [!] Requested: 100 workers (400 vCPUs)
#> [!] Current quota: 100 vCPUs (allows 25 workers max)
#>
#> [Plan] Execution plan:
#> - Running in 4 waves of 25 workers each
#>
#> [TIP] Request quota increase to 500 vCPUs? [y/n]: y
#>
#> [OK] Quota increase requested
#> [Starting] Starting wave 1 (25 workers)...
results <- future_map(inputs, expensive_function)
#> [Wave] Wave 1: 100% complete (250 tasks)
#> [Wave] Wave 2: 100% complete (500 tasks)
#> [Wave] Wave 3: 100% complete (750 tasks)
#> [Wave] Wave 4: 100% complete (1000 tasks)Environment mismatch: Packages not found on workers
Task failures: Some tasks failing
# Check logs
starburst_logs(task_id = "failed-task-id")
# Often due to memory limits - increase worker memory
plan(starburst, workers = 50, memory = "16GB") # Default is 8GBSlow data transfer: Large objects taking too long
✅ Good: Each task takes >5 minutes
❌ Bad: Each task takes <1 minute
Instead of:
Do:
Don’t:
big_data <- read.csv("10GB_file.csv") # Upload for every task
results <- future_map(1:1000, function(i) process(big_data, i))Do:
# Upload once to S3
tmp <- tempfile(fileext = ".csv"); write.csv(big_data, tmp, row.names = FALSE)
paws.storage::s3()$put_object(Bucket = "bucket", Key = "big_data.csv", Body = tmp)
# Workers read from S3 (read_s3 helper from "Working with Data" above)
results <- future_map(tasks, function(i) {
data <- read_s3("s3://bucket/big_data.csv", reader = read.csv)
process(data, i)
})