Cloud platforms offer instance "families" tuned for different workloads — compute-optimized, memory-optimized, and general-purpose. The difference is the ratio of CPU to RAM, and picking the wrong family means paying for a resource you don't use while starving the one you do. This guide helps you identify your real bottleneck and choose accordingly.
What the families mean
The core distinction is the CPU-to-memory ratio:
| Family | CPU:RAM ratio | Tuned for |
|---|---|---|
| Compute-optimized | High CPU per GB | CPU-bound work |
| Memory-optimized | High RAM per CPU | Memory-bound work |
| General-purpose | Balanced | Mixed / unknown |
If an app needs lots of CPU but little memory, a memory-optimized tier wastes money on RAM it never touches — and vice versa. Matching the family to your bottleneck is how you get efficient cost-per-unit-of-work.
The key question: what's your bottleneck?
Everything comes down to identifying which resource saturates first under load. Run a realistic load test and watch both curves:
- CPU saturates first (pinned near 100%, memory comfortable) → compute-optimized.
- Memory saturates first (climbing toward the ceiling, CPU comfortable) → memory-optimized.
- Both rise together → general-purpose.
Don't guess from intuition — measure. Many "slow" apps people assume are CPU-bound turn out to be memory-bound (or I/O-bound, which neither family fixes).
Workloads that are typically CPU-bound
Reach for compute-optimized when your app spends its time *calculating*:
- Image/video transcoding and processing
- Compression / encryption / hashing
- High-RPS APIs doing real work per request (serialization, templating)
- Scientific/numeric computation
- Compilation or rendering in-request
- Game servers with physics/simulation
The tell: CPU pegged, memory mostly idle.
Workloads that are typically memory-bound
Reach for memory-optimized when your app *holds a lot in memory*:
- In-memory caches (Redis-style data, large LRU caches)
- Large in-memory datasets or analytics on big working sets
- Runtimes with big heaps (JVM apps, large Node processes)
- Data processing that loads big structures (large DataFrames)
- Many concurrent connections each holding buffers
The tell: memory climbing toward the limit (risking OOM / exit code 137) while CPU stays low.
A worked example
Say you run an image-thumbnail API. Under load you observe CPU pinned at 100% while memory sits at 30%. That's textbook compute-optimized — you need more CPU, and paying for extra RAM you don't use would be waste.
Now say you run an analytics service that loads a 6 GB dataset into memory and answers queries against it. Memory is the constraint; CPU is moderate. That's memory-optimized — you need RAM density, and compute-optimized would force you to over-buy CPU just to get enough memory.
Same company, two services, two different correct answers. Tier choice is per-workload, not per-account.
Don't forget: it might be neither
A crucial honest caveat. If your app is slow but both CPU and memory are comfortable, the bottleneck is elsewhere and *no* instance family will fix it:
- I/O-bound: waiting on disk or network.
- Database-bound: slow queries; the app is idle waiting on the DB. Fix the query/indexes, not the instance.
- External-API-bound: blocked on third-party calls.
- Lock contention / single-threaded bottleneck: more cores won't help a single-threaded hot path.
Throwing a bigger tier at an I/O- or database-bound app is a common, expensive mistake. Profile before you upsize.
The Node.js / JVM heap reconciliation
Whichever family you pick, make sure your runtime knows the memory limit. Node's default old-space and the JVM's default heap can mismatch a small container and OOM before they GC:
# Node: cap heap below the container memory limit
NODE_OPTIONS="--max-old-space-size=3072" # ~4GB container# JVM: size heap as a percentage of container memory
JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75"On a memory-optimized tier especially, you want the runtime to actually *use* the RAM you paid for.
Mapping to PandaStack tiers
PandaStack offers a range of compute tiers, including compute-optimized (c1, c2) and memory-optimized (m1, m2) families, alongside a small free tier (0.25 CPU / 512 MB) for dev and hobby use and up to C2-2XCompute (8 CPU / 16 GB) at the top of the range. The decision procedure:
- 1Deploy on a modest tier.
- 2Load test and watch the CPU and memory curves (PandaStack captures server-side metrics — no client SDK).
- 3Identify which saturates first.
- 4Move to the matching family —
cfor CPU-bound,mfor memory-bound, general-purpose if balanced. - 5Re-measure; adjust.
Because changing tiers is a dropdown rather than a migration, you can iterate cheaply until utilization sits in a comfortable band.
Quick decision table
| Symptom under load | Bottleneck | Pick |
|---|---|---|
| CPU ~100%, RAM low | CPU | Compute-optimized (c1/c2) |
| RAM near limit, CPU low | Memory | Memory-optimized (m1/m2) |
| Both rising together | Balanced | General-purpose |
| Both low but app slow | I/O / DB / external | Fix the code/query, not the tier |
References
- [AWS EC2 instance types (families)](https://aws.amazon.com/ec2/instance-types/)
- [Google Cloud machine families](https://cloud.google.com/compute/docs/machine-resource)
- [Kubernetes resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)
- [Node.js
--max-old-space-size](https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib) - [JVM container awareness (
MaxRAMPercentage)](https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html)
---
PandaStack makes tier selection low-stakes: measure with built-in server-side metrics, then switch between compute- and memory-optimized tiers with a dropdown. Start sizing on the free tier at [dashboard.pandastack.io](https://dashboard.pandastack.io).