---
name: aws-compute-and-storage
description: >-
  AWS compute and storage fundamentals: right-sizing Lambda and EC2 against
  measured usage, S3 lifecycle policies, avoiding ephemeral-disk state loss,
  and deliberate autoscaling bounds. Use when provisioning or reviewing
  compute and storage resources.
argument-hint: "[resource]"
license: MIT
metadata:
  author: John Felix Lim
  version: "1.0.0"
---
# Core Compute & Storage

Apply when provisioning compute or storage, or reviewing cost or reliability. Check whether sizing decisions are backed by actual metrics before assuming a default or an initial guess is still correct.

## Rules

1. **Lambda cold starts are a design constraint, not a bug to eliminate** — A function outside a VPC with a small deployment package starts fast; one that pulls in a large dependency tree or sits in a VPC pays a real cold-start cost. Design the timeout and user experience around this — don't pretend it doesn't exist.
2. **Right-size compute against measured usage, not a guess** — An EC2 instance or Lambda memory allocation chosen once at launch and never revisited is either wasted spend or a silent bottleneck. CloudWatch metrics exist specifically to answer this.
3. **S3 lifecycle policies are the difference between cheap storage and an ever-growing bill** — Objects that don't get moved to Infrequent Access or Glacier, or expired, accumulate storage cost indefinitely for data nobody's read in years.
4. **Ephemeral compute shouldn't hold state it can't afford to lose** — A Lambda's `/tmp` or an ECS task's local disk can vanish between invocations or on a task restart. Anything that must survive belongs in S3, RDS, or DynamoDB, not local disk.
5. **Autoscaling needs both a floor and a ceiling stated deliberately** — A minimum that's too low means a cold cluster taking the first burst of traffic badly. A maximum that's unset, or too high, means a runaway process or attack can scale cost as fast as it scales instances.

## Patterns

### Lambda memory left at the default

**Avoid** — The default memory allocation wasn't chosen for this workload's actual CPU needs, and Lambda's proportional CPU scaling means it's quietly starved.

```hcl
resource "aws_lambda_function" "process_image" {
  memory_size = 128 # default, never revisited
  timeout     = 30
}
# CPU is allocated proportionally to memory in Lambda — 128MB
# gives this CPU-bound image processing job a fraction of a
# vCPU, so it runs slowly and sometimes times out.
```

**Prefer** — Memory (and the CPU that scales with it) is sized against measured behaviour, not left at whatever the default happened to be.

```hcl
resource "aws_lambda_function" "process_image" {
  memory_size = 1024 # sized against actual CloudWatch duration/memory metrics
  timeout     = 30
}
```

### No S3 lifecycle policy on an append-only bucket

**Avoid** — Storage cost grows linearly and permanently with log volume, even for logs from three years ago that will never be read again.

```hcl
resource "aws_s3_bucket" "logs" {
  bucket = "app-access-logs"
}
# No lifecycle rule — every log object ever written stays in
# Standard storage class forever.
```

**Prefer** — Objects age into cheaper storage automatically and eventually expire, so cost tracks actual retention needs instead of growing forever.

```hcl
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
  bucket = aws_s3_bucket.logs.id
  rule {
    id     = "age-out-logs"
    status = "Enabled"
    transition { days = 30  storage_class = "STANDARD_IA" }
    transition { days = 90  storage_class = "GLACIER" }
    expiration { days = 365 }
  }
}
```

### State written to ephemeral local disk

**Avoid** — `/tmp` is scoped to one execution environment and isn't guaranteed to persist or be shared across invocations.

```ts
// handler.ts — writes a processing checkpoint to /tmp, assuming
// it'll be there on the next invocation.
import { writeFileSync } from "node:fs";

export const handler = async (state: unknown) => {
  writeFileSync("/tmp/checkpoint.json", JSON.stringify(state));
  // The next invocation may hit a cold, different execution
  // environment where /tmp is empty.
};
```

**Prefer** — The checkpoint survives regardless of which execution environment handles the next invocation, because it lives in genuinely durable storage.

```ts
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

export const handler = async (state: unknown, jobId: string) => {
  const s3 = new S3Client({});
  await s3.send(new PutObjectCommand({
    Bucket: "app-checkpoints",
    Key: `${jobId}.json`,
    Body: JSON.stringify(state),
  }));
};
```

## Review checklist

- [ ] Lambda memory/CPU and EC2 instance sizing are set against measured CloudWatch metrics, not left at defaults or initial guesses.
- [ ] Every S3 bucket that accumulates objects over time has an explicit lifecycle policy (transition and/or expiration).
- [ ] Nothing that must survive between invocations or restarts is written only to ephemeral local disk (`/tmp`, container filesystem).
- [ ] Autoscaling groups and Lambda concurrency have both a deliberate floor and an explicit ceiling.
- [ ] Cold-start-sensitive Lambda functions avoid unnecessary VPC attachment and heavy dependency trees.
- [ ] Compute and storage costs are reviewed periodically against actual usage, not set once at launch and forgotten.

---

From the John Felix Lim Frontend Engineering Playbook — https://github.com/JohnFelixLim