Service Capacity Modeling
A generic toolkit for modeling capacity requirements in the cloud. Pricing information included in this repository are public prices.
NOTE: Netflix confidential information should never enter this repo. Please remember this repository is public when making changes to it.
Trying it out
Run the tests:
# Test the capacity planner on included netflix models
$ tox -e py310
# Run a single test with a debugger attached if the test fails
$ .tox/py310/bin/pytest -n0 -k test_java_heap_heavy --pdb --pdbcls=IPython.terminal.debugger:Pdb
# Verify all type contracts
$ tox -e mypy
Run IPython for interactively using the library:
tox -e dev -- ipython
Example of Provisioning a Database
Fire up ipython and let's capacity plan a Tier 1 (important to the product aka "prod") Cassandra database.
from service_capacity_modeling.interface import CapacityDesires
from service_capacity_modeling.interface import FixedInterval, Interval
from service_capacity_modeling.interface import QueryPattern, DataShape
db_desires = CapacityDesires(
# This service is important to the business, not critical (tier 0)
service_tier=1,
query_pattern=QueryPattern(
# Not sure exactly how much QPS we will do, but we think around
# 10,000 reads and 10,000 writes per second.
estimated_read_per_second=Interval(
low=1000, mid=10000, high=100000, confidence=0.9
),
estimated_write_per_second=Interval(
low=1000, mid=10000, high=100000, confidence=0.9
),
),
# Not sure how much data, but we think it'll be below 1 TiB
data_shape=DataShape(
estimated_state_size_gib=Interval(low=100, mid=100, high=1000, confidence=0.9),
),
)
Now we can load up some models and do some capacity planning
from service_capacity_modeling.capacity_planner import planner
from service_capacity_modeling.models.org import netflix
import pprint
# Load up the Netflix capacity models
planner.register_group(netflix.models)
cap_plan = planner.plan(
model_name="org.netflix.cassandra",
region="us-east-1",
desires=db_desires,
# Simulate the possible requirements 512 times
simulations=512,
# Request 3 diverse hardware families to be returned
num_results=3,
)
# The range of requirements in hardware resources (CPU, RAM, Disk, etc ...)
requirements = cap_plan.requirements
# The ordered list of least regretful choices for the requirement
least_regret = cap_plan.least_regret
# Show the range of requirements for a single zone
pprint.pprint(requirements.zonal[0].model_dump())
# Show our least regretful choices of hardware in least regret order
# So for example if we can buy the first set of computers we would prefer
# to do that but we might not have availability in that family in which
# case we'd buy the second one.
for choice in range(3):
num_clusters = len(least_regret[choice].candidate_clusters.zonal)
print(f"Our #{choice + 1} choice is {num_clusters} zones of:")
pprint.pprint(least_regret[choice].candidate_clusters.zonal[0].model_dump())
Note that we can customize more information given what we know about the use case, but each model (e.g. Cassandra) supplies reasonable defaults.
For example we can specify a lot more information
from service_capacity_modeling.interface import CapacityDesires, QueryPattern, Interval, FixedInterval, DataShape
db_desires = CapacityDesires(
# This service is important to the business, not critical (tier 0)
service_tier=1,
query_pattern=QueryPattern(
# Not sure exactly how much QPS we will do, but we think around
# 50,000 reads and 45,000 writes per second with a rather narrow
# bound
estimated_read_per_second=Interval(
low=40_000, mid=50_000, high=60_000, confidence=0.9
),
estimated_write_per_second=Interval(
low=42_000, mid=45_000, high=50_000, confidence=0.9
),
# This use case might do some partition scan queries that are
# somewhat expensive, so we hint a rather expensive ON-CPU time
# that a read will consume on the entire cluster.
estimated_mean_read_latency_ms=Interval(
low=0.1, mid=4, high=20, confidence=0.9
),
# Writes at LOCAL_ONE are pretty cheap
estimated_mean_write_latency_ms=Interval(
low=0.1, mid=0.4, high=0.8, confidence=0.9
),
# We want single digit latency, note that this is not a p99 of 10ms
# but defines the interval where 98% of latency falls to be between
# 0.4 and 10 milliseconds. Think of:
# low = "the minimum reasonable latency"
# high = "the maximum reasonable latency"
# mid = "value between low and high such that I want my distribution
# to skew left or right"
read_latency_slo_ms=FixedInterval(
low=0.4, mid=4, high=10, confidence=0.98
),
write_latency_slo_ms=FixedInterval(
low=0.4, mid=4, high=10, confidence=0.98
)
),
# Not sure how much data, but we think it'll be below 1 TiB
data_shape=DataShape(
estimated_state_size_gib=Interval(low=100, mid=500, high=1000, confidence=0.9),
),
)
Example of provisioning a caching cluster
In this example we tweak the QPS up, on CPU time of operations down and SLO down. This more closely approximates a caching workload
from service_capacity_modeling.interface import CapacityDesires, QueryPattern, Interval, FixedInterval, DataShape
from service_capacity_modeling.capacity_planner import planner
cache_desires = CapacityDesires(
service_tier=1,
query_pattern=QueryPattern(
# Not sure exactly how much QPS we will do, but we think around
# 10,000 reads and 10,000 writes per second.
estimated_read_per_second=Interval(
low=10_000, mid=100_000, high=1_000_000, confidence=0.9
),
estimated_write_per_second=Interval(
low=1_000, mid=20_000, high=100_000, confidence=0.9
),
# Memcache is consistently fast at queries
estimated_mean_read_latency_ms=Interval(
low=0.05, mid=0.2, high=0.4, confidence=0.9
),
estimated_mean_write_latency_ms=Interval(
low=0.05, mid=0.2, high=0.4, confidence=0.9
),
# Caches usually have tighter SLOs
read_latency_slo_ms=FixedInterval(
low=0.4, mid=0.5, high=5, confidence=0.98
),
write_latency_slo_ms=FixedInterval(
low=0.4, mid=0.5, high=5, confidence=0.98
)
),
# Not sure how much data, but we think it'll be below 1000
data_shape=DataShape(
estimated_state_size_gib=Interval(low=100, mid=200, high=500, confidence=0.9),
),
)
cache_cap_plan = planner.plan(
model_name="org.netflix.cassandra",
region="us-east-1",
desires=cache_desires,
allow_gp2=True,
)
requirement = cache_cap_plan.requirement
least_regret = cache_cap_plan.least_regret
Notebooks
We have a demo notebook in notebooks you can use to experiment. Start it with
tox -e notebook -- jupyter notebook notebooks/demo.ipynb
Development
To contribute to this project:
- Make your change in a branch. Consider making a new model if you are making significant changes and registering it as a different name.
- Write a unit test using
pytestin thetestsfolder. - Ensure your tests pass via
toxor debug them with:
tox -e py310 -- -k test_<your_functionality> --pdb --pdbcls=IPython.terminal.debugger:Pdb
Pre-commit / Linting
To run the linting manually:
tox -e pre-commit
Installing Pre-commit Hooks
This repository includes a custom pre-commit hook that runs all linting and formatting checks through the tox environment. To install it:
# Install the custom pre-commit hook
tox -e install-hooks
# Or manually copy the hook
cp hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
The hook will automatically:
- Create the tox pre-commit environment if it doesn't exist
- Run all pre-commit checks (ruff, flake8, etc.)
- Ensure all code quality standards are met before commits
PyCharm IDE Setup
Use one of the test environments for IDE development, e.g. tox -e py310 and then
Add New Interpreter -> Add Local -> Select Existing -> Navigate to (workdir)/.tox/py310.
Running CLIs
Use the dev virtual environment via tox -e dev. Then execute CLIs via that env.
AWS instance shape lifecycle
Lifecycle states
Each hardware Instance (and Drive) shape carries a lifecycle field (see
Lifecycle in service_capacity_modeling/interface.py) describing how much to
trust its parameters and whether the capacity planner should recommend it by
default:
| Lifecycle | Semantic | Used by default? | Opt-in |
|---|---|---|---|
alpha |
Hardware parameters (e.g. cpu_ipc_scale) are not yet benchmarked and should be treated as provisional, such as a family that was just announced or is still in preview. |
No | Pass a lifecycles= sequence including Lifecycle.alpha to the planner call, or request the family/instance name explicitly (name-based selection bypasses the lifecycle filter entirely). |
beta |
Parameters are reasonably trusted but the family hasn't accumulated much production track record yet. | Yes | N/A — already in the default set. |
stable |
Well-understood, production-proven hardware. Shapes with no explicit lifecycle are treated as stable. |
Yes | N/A — already in the default set. |
deprecated |
Still usable but no longer preferred; typically means AWS or Netflix has signaled a replacement exists. | No | Pass a lifecycles= sequence including Lifecycle.deprecated, or request the family/instance name explicitly. |
end-of-life |
No longer available/supported. | No | Pass a lifecycles= sequence including Lifecycle.end_of_life, or request the family/instance name explicitly. |
Adding a new instance family
- Register the family in
service_capacity_modeling/tools/instance_families.py, adding an entry toINSTANCE_TYPESkeyed by family name (e.g."m8a"). Always setlifecycleto"alpha"if the parameters above are still provisional (see Lifecycle states). Once it's benchmarked, promote the lifecycle to"beta"to make it available by default. - Generate the shape JSON by running:
This requires live AWS credentials. This queries AWS and writespython -m service_capacity_modeling/tools/generate_missing --executeauto_<family>.json. A single family can also be regenerated directly viaauto_shape.pywith explicit flags. - Commit and push to the main branch.
- Pricing is fetched separately via
fetch_pricing.py(AWS Pricing API) and written underhardware/profiles/pricing/aws/, matched to shapes by filename prefix.
Validation tools
-
pytest —
tests/test_hardware_shapes.py: runs on everypytest/toxinvocation (not part of pre-commit). Checks cross-family invariants over all loaded shapes — consistent vCPU counts for the same size across generations, performance (cpu_ghz * cpu_ipc_scale) increasing from one generation to the next, RAM/vCPU ratio consistency within a family, and network bandwidth scaling non-decreasingly with instance size. -
Pre-commit
capture-baselinehook: (tox -e capture-baseline) re-runs capacity-planning scenarios and diffs the resulting cost/recommendation output against the checked-in baseline, to catch unintended shifts in planner behavior caused by the new or changed shape.
Release
Any successful main build will trigger a release to PyPI, defaulting to a patch bump based on the setupmeta
distance algorithm. If
you are significantly adding to the API please follow the below instructions to bump the base version. Since we
are still in 0. we do not do major version bumps.
Bumping a minor or major
From latest main, bump at least the minor to get a new base version:
git tag v0.4.0
git push origin HEAD --tags
Now setupmeta will bump the patch from this version, e.g. 0.4.1.
Release files for service-capacity-modeling 0.3.209
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| service_capacity_modeling-0.3.209.tar.gz | 244.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| service_capacity_modeling-0.3.209-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 501.8 kB
Release files / service_capacity_modeling-0.3.209.tar.gz
| Download URL | service_capacity_modeling-0.3.209.tar.gz |
|---|---|
| Size | 244.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e5f7e6f4ffc0dbf5e9309de666c8295afc1adff9560c6eeb152832031ff17b62
|
|
BLAKE2b-256 checksum How to use checksums |
f0ec1bba4b2dd5b97e9c3d5877f11da932ed47e70ef97d6113234b6277031bae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / service_capacity_modeling-0.3.209-py3-none-any.whl
| Download URL | service_capacity_modeling-0.3.209-py3-none-any.whl |
|---|---|
| Size | 257.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b20c3155779187b9599a678b60c123c18dce064a617afd24195f7c85a12565c0
|
|
BLAKE2b-256 checksum How to use checksums |
1856c1ea3e6766a61dcc031d395bf2e8791568ba5338fa0e1a8ccf6d91940aad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log