---
title: "Getting Started with staRburst"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting Started with staRburst}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE
)
```

# Introduction

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.

## Which API should I use?

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](#migrating-existing-future-furrr-code);
the two styles are equivalent and shown side by side there.

### Which backend? EC2 (default) vs Fargate

Every API above runs on one of two compute backends, chosen with `launch_type`:

- **EC2 — the default and recommended choice.** Faster (no cold start), cheaper with
  Spot (the default), and more flexible (any instance type, warm pools).
  `starburst_setup()` provisions its capacity provider for you, so it works out of
  the box.
- **Fargate — an optional serverless alternative** (`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.

## How it works (the mental model)

```
        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:**

- **Credentials** are used only on your machine (to call AWS) and by the workers'
  IAM role — you never put keys in your code or in S3.
- **Uploaded** per task: your function, its inputs, and auto-detected globals — not
  your whole workspace. Large data should go to S3 yourself and be read on the worker.
- **Dependencies** come from your `renv.lock`, installed into the worker image once
  and cached in ECR (this is the first-run build cost).
- **After a failure or disconnect**, workers keep running against S3 until a
  timeout; nothing is silently lost. `session$cleanup()` (or normal completion of
  `starburst_map()`) tears down workers and removes the job's S3 objects.
- **Detached sessions** differ only in step 3's return path: results persist in S3
  so you can close R and collect later, instead of blocking the local session.

## Installation

```{r}
# Install from GitHub
remotes::install_github("scttfrdmn/starburst")
```

## One-Time Setup

Before using staRburst, you need to configure AWS resources. This only needs to be done once.

```{r}
library(starburst)

# Interactive setup wizard
starburst_setup()
```

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).

## Your first job

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:

```{r}
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.85
```

That is the whole workflow: `starburst_setup()` once, then `starburst_map()` per
job. Everything below builds on this.

## Migrating existing `future` / `furrr` code {#migrating-existing-future-furrr-code}

If 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:

```{r}
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] TRUE
```

The two calls below do the same thing — pick the one that fits your codebase:

```{r}
# Direct API
results <- starburst_map(1:100, expensive_simulation, workers = 50)

# future / furrr
plan(starburst, workers = 50)
results <- future_map(1:100, expensive_simulation)
```

## Real-world examples

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:

- [Monte Carlo simulation](https://starburst.ing/articles/example-monte-carlo.html)
  — many small simulations batched into a handful of tasks.
- [Bootstrap resampling](https://starburst.ing/articles/example-bootstrap.html)
  — resampling a shared dataset across workers.
- [Grid search](https://starburst.ing/articles/example-grid-search.html),
  [risk modeling](https://starburst.ing/articles/example-risk-modeling.html),
  [geospatial](https://starburst.ing/articles/example-geospatial.html), and
  [more](https://starburst.ing/articles/).

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](https://starburst.ing/articles/workload-shapes.html) and
[Performance](https://starburst.ing/articles/performance.html). 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](#batch-small-tasks) below.

## Working with Data

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`):

```{r}
# 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)
}
```

### Data Already in S3

If your data is already in S3, workers can read it directly:

```{r}
plan(starburst, workers = 50)

results <- future_map(file_list, function(file) {
  # Each worker pulls its file from S3 (read_s3 defined above)
  data <- read_s3(sprintf("s3://my-bucket/%s", file), reader = read.csv)
  process(data)
})
```

### Uploading Local Data

For smaller datasets, you can pass data as arguments:

```{r}
# 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)
})
```

### Large Data Optimization

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:

```{r}
# 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)
})
```

## Cost Management

### Estimate Costs

```{r}
# Check cost before running
plan(starburst, workers = 100, cpu = 4, memory = "8GB")
#> Estimated cost: ~$3.50/hour
```

### Set Cost Limits

```{r}
# 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)
```

### Cost estimates

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.

```{r}
plan(starburst, workers = 50)

results <- future_map(data, process)

#> Cluster runtime: ~23 minutes
#> Estimated cost: ~$1.34
```

## Quota Management (Fargate backend)

> 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.

### Check Your Quota

```{r}
starburst_quota_status()
#> Fargate vCPU Quota: 100 / 100 used
#> Allows: ~25 workers with 4 vCPUs each
#>
#> Recommended: Request increase to 500 vCPUs
```

### Request Quota Increase

```{r}
starburst_request_quota_increase(vcpus = 500)
#> Requesting Fargate vCPU quota increase:
#>   Current: 100 vCPUs
#>   Requested: 500 vCPUs
#>
#> [OK] Quota increase requested (Case ID: 12345678)
#> [OK] AWS typically approves within 1-24 hours
```

### Wave-Based Execution

If you request more workers than your quota allows, staRburst automatically uses wave-based execution:

```{r}
# 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)
```

## Troubleshooting

### View Worker Logs

```{r}
# View logs from most recent cluster
starburst_logs()

# View logs from specific task
starburst_logs(task_id = "abc-123")

# View last 100 log lines
starburst_logs(last_n = 100)
```

### Check Cluster Status

```{r}
starburst_status()
#> Active Clusters:
#>   • starburst-xyz123: 50 workers running
#>   • starburst-abc456: 25 workers running
```

### Common Issues

**Environment mismatch**: Packages not found on workers

```{r}
# Rebuild environment
starburst_rebuild_environment()
```

**Task failures**: Some tasks failing

```{r}
# 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 8GB
```

**Slow data transfer**: Large objects taking too long

```{r}
# Use Arrow for data frames
library(arrow)
write_parquet(my_data, "s3://bucket/data.parquet")

# Workers read Arrow
results <- future_map(1:100, function(i) {
  data <- read_parquet("s3://bucket/data.parquet")
  process(data, i)
})
```

## Best Practices

### 1. Use for Right-Sized Workloads

✅ **Good**: Each task takes >5 minutes
```{r}
# 100 tasks, each takes 10 minutes
# Local: 1000 minutes, Cloud: ~10 minutes
```

❌ **Bad**: Each task takes <1 minute
```{r}
# 10000 tasks, each takes 30 seconds
# Startup overhead (45s) dominates
```

### 2. Batch Small Tasks

Instead of:
```{r}
# 10,000 tiny tasks
results <- future_map(1:10000, small_function)
```

Do:
```{r}
# 100 batches of 100 tasks each
batches <- split(1:10000, ceiling(seq_along(1:10000) / 100))

results <- future_map(batches, function(batch) {
  lapply(batch, small_function)
})

# Flatten results
results <- unlist(results, recursive = FALSE)
```

### 3. Use S3 for Large Data

Don't:
```{r}
big_data <- read.csv("10GB_file.csv")  # Upload for every task
results <- future_map(1:1000, function(i) process(big_data, i))
```

Do:
```{r}
# 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)
})
```

### 4. Set Reasonable Limits

```{r}
starburst_config(
  max_hourly_cost = 50,            # Cap the hourly rate (USD/hour)
  cost_alert_threshold = 25        # Get warned early
)
```

### 5. Clean Up

```{r}
# staRburst auto-cleans, but you can force it
plan(sequential)  # Switch back to local
# Old cluster resources are cleaned up automatically
```

## Advanced: Custom Configuration

### CPU and Memory

```{r}
# High CPU, low memory (CPU-bound work)
plan(starburst, workers = 50, cpu = 8, memory = "16GB")

# Low CPU, high memory (memory-bound work)
plan(starburst, workers = 25, cpu = 4, memory = "32GB")
```

### Timeout

```{r}
# Increase timeout for long-running tasks (default 1 hour)
plan(starburst, workers = 10, timeout = 7200)  # 2 hours
```

### Region

```{r}
# Use specific region (default from config)
plan(starburst, workers = 50, region = "us-west-2")
```

## Next Steps

- Learn about [Detached Sessions](detached-sessions.html) for long-running jobs
- Explore [Example Vignettes](https://starburst.ing/articles/) for real-world patterns
- Review [Security Best Practices](security.html) guide
- Read [Troubleshooting Guide](troubleshooting.html) when stuck

## Getting Help

- GitHub Issues: https://github.com/scttfrdmn/starburst/issues
- Email: help@starburst.ing
- Documentation: https://starburst.ing
