Configuration Management — dask_setup Recipe
This notebook demonstrates advanced configuration management in dask_setup, covering:
Using built-in profiles (
climate_analysis,interactive,development, etc.)Building explicit
DaskSetupConfigobjects with full parameter controlLoading custom YAML profiles
Using
ConfigManagerto list, inspect and manage profilesConfiguration best practices
Note: Each demo creates and closes its own cluster so resources are fully independent between examples.
Setup and Imports
import sys
import time
from pathlib import Path
import dask
import dask.array as da
import numpy as np
from dask_setup import setup_dask_client
from dask_setup.config import DaskSetupConfig
from dask_setup.config_manager import ConfigManager
print("Imports OK")
Imports OK
1. Built-in Profiles
dask_setup ships a set of pre-tuned profiles for common scientific computing patterns.
Use ConfigManager to list and inspect them.
manager = ConfigManager()
profiles = manager.list_profiles()
builtin = {name: p for name, p in profiles.items() if p.builtin}
print(f"Built-in profiles ({len(builtin)}):")
for name, p in builtin.items():
cfg = p.config
print(f" {name:20s} workload={cfg.workload_type:6s} reserve={cfg.reserve_mem_gb} GB "
f"adaptive={cfg.adaptive}")
Built-in profiles (5):
climate_analysis workload=cpu reserve=60.0 GB adaptive=False
zarr_io_heavy workload=io reserve=40.0 GB adaptive=False
development workload=mixed reserve=8.0 GB adaptive=False
production workload=mixed reserve=80.0 GB adaptive=True
interactive workload=mixed reserve=20.0 GB adaptive=False
# Use a built-in profile — development is safe to run anywhere
client, cluster, tmp = setup_dask_client(profile="development")
print(f"Workers: {len(client.nthreads())}")
print(f"Temp dir: {tmp}")
# Quick benchmark
t0 = time.perf_counter()
x = da.random.random((2000, 2000), chunks=(500, 500))
result = (x + x.T).mean().compute()
elapsed = time.perf_counter() - t0
print(f"Result: {result:.6f} wall={elapsed:.2f}s")
client.close(); cluster.close()
[setup_dask_client] Configuration summary
temp/spill dir: /tmp/dask-2426
Workers: 2 | threads/worker: 2 | processes: True
Memory: total ~15.6 GiB | usable ~7.6 GiB | per-worker ~3.8 GiB
Compression: spill=auto | comm=False
Workers: 2
Temp dir: /tmp/dask-2426
Result: 0.999762 wall=0.44s
2. Explicit DaskSetupConfig Object
For reproducible, version-controlled configurations, build a DaskSetupConfig directly.
Every parameter has a sensible default, so you only need to specify what differs from the base.
reserve_mem_gbin config objects: called with no config,setup_dask_client()reserves 20% of RAM (clamped to 4–50 GB). ADaskSetupConfiginstead carries a flat 50 GB default, because profiles need a concrete number to save, and that is more than a laptop has. Set it explicitly in any config you build.
config = DaskSetupConfig(
workload_type="mixed",
max_workers=4,
reserve_mem_gb=4.0, # set explicitly: the config-object default is 50 GB
spill_compression="auto", # lz4 if installed, else snappy, else none
memory_target=0.70,
memory_spill=0.80,
dashboard=True,
description="Custom mixed workload — 4 workers",
)
print("Config object:")
print(f" workload_type : {config.workload_type}")
print(f" max_workers : {config.max_workers}")
print(f" reserve_mem_gb : {config.reserve_mem_gb}")
print(f" spill_compression: {config.spill_compression}")
print(f" memory_target : {config.memory_target}")
client, cluster, tmp = setup_dask_client(config=config)
print(f"\nCluster started — {len(client.nthreads())} workers, tmp={tmp}")
# Demonstration workload
data = da.random.random((1500, 1500), chunks=(300, 300))
result = (data * 2 + da.sin(data)).mean().compute()
print(f"Result: {result:.6f}")
client.close(); cluster.close()
Config object:
workload_type : mixed
max_workers : 4
reserve_mem_gb : 4.0
spill_compression: auto
memory_target : 0.7
[setup_dask_client] Configuration summary
temp/spill dir: /tmp/dask-2426
Workers: 2 | threads/worker: 2 | processes: True
Memory: total ~15.6 GiB | usable ~11.6 GiB | per-worker ~5.8 GiB
Compression: spill=auto | comm=False
Cluster started — 2 workers, tmp=/tmp/dask-2426
Result: 1.460066
3. Inline dask.config Overrides
For fine-grained Dask scheduler/worker settings, use dask.config.set() as a context manager
alongside setup_dask_client.
with dask.config.set({
"distributed.scheduler.idle-timeout": "30s",
"distributed.worker.daemon": False,
"distributed.worker.memory.rebalance.measure": "managed_in_memory",
}):
print("Active overrides:")
print(f" idle-timeout : {dask.config.get('distributed.scheduler.idle-timeout')}")
print(f" worker.daemon: {dask.config.get('distributed.worker.daemon')}")
client, cluster, tmp = setup_dask_client(
workload_type="mixed",
max_workers=2,
dashboard=False,
)
print(f"Workers: {len(client.nthreads())}")
data = da.random.random((1000, 1000), chunks=(200, 200))
result = (data * 2).mean().compute()
print(f"Result: {result:.6f}")
client.close(); cluster.close()
Active overrides:
idle-timeout : 30s
worker.daemon: False
[setup_dask_client] Configuration summary
temp/spill dir: /tmp/dask-2426
Workers: 2 | threads/worker: 2 | processes: True
Memory: total ~15.6 GiB | usable ~11.6 GiB | per-worker ~5.8 GiB
Compression: spill=auto | comm=False
Workers: 2
Result: 0.999540
4. Custom YAML Profiles
Save named configurations as YAML files for sharing across a team or project. A profile keeps its
settings under a config: key; configs/cpu_profile.yaml ships with this recipe as an example.
⚠️ A YAML file with its settings at the top level (no
config:key) still loads, but every setting in it is ignored and you get the defaults. Checkprofile.configafter loading.
manager = ConfigManager()
profile = manager.load_profile_from_file(Path("../configs/cpu_profile.yaml"))
print(f"Profile : {profile.name}")
print(f"Description : {profile.description}")
cfg = profile.config
print(f" workload_type : {cfg.workload_type}")
print(f" reserve_mem_gb : {cfg.reserve_mem_gb}")
print(f" spill_compression: {cfg.spill_compression}")
print(f" tags : {cfg.tags}")
# Use it, overriding what doesn't suit this machine: explicit arguments always win.
# (This profile reserves 60 GB, sized for a Gadi node, not a laptop.)
client, cluster, tmp = setup_dask_client(
config=cfg, max_workers=2, reserve_mem_gb=4.0, dashboard=False
)
print(f"\nStarted {len(client.nthreads())} workers from '{profile.name}'")
client.close(); cluster.close()
Profile : cpu_intensive
Description : Heavy computation workload optimized for mathematical operations
workload_type : cpu
reserve_mem_gb : 60.0
spill_compression: auto
tags : ['cpu-heavy', 'mathematical', 'scientific', 'single-threaded']
[setup_dask_client] Configuration summary
temp/spill dir: /tmp/dask-2426
Workers: 2 | threads/worker: 1 | processes: True
Memory: total ~15.6 GiB | usable ~11.6 GiB | per-worker ~5.8 GiB
Compression: spill=auto | comm=False
Started 2 workers from 'cpu_intensive'
import tempfile
from dask_setup.config import ConfigProfile
# Save a profile. Normally you would use ConfigManager(), which saves to
# ~/.dask_setup/profiles/; a temporary directory keeps this demo self-contained.
demo_manager = ConfigManager(config_dir=tempfile.mkdtemp(prefix="dask_setup_profiles_"))
demo_manager.save_profile(
ConfigProfile(
name="zarr_pipeline",
config=DaskSetupConfig(
workload_type="io", # Zarr releases the GIL, so threads read in parallel
reserve_mem_gb=20.0,
description="Zarr-backed analysis: one process, many threads",
tags=["zarr", "io"],
),
)
)
saved = demo_manager.config_dir / "profiles" / "zarr_pipeline.yaml"
print(f"Profile written to: {saved}\n")
print(saved.read_text())
# Profiles are looked up user -> site -> builtin, so this is now visible by name
print("Known profiles:", sorted(demo_manager.list_profiles()))
Profile written to: /tmp/dask_setup_profiles_1_9s1iw_/profiles/zarr_pipeline.yaml
builtin: false
config:
adaptive: false
comm_compression: false
dashboard: true
dashboard_port: null
description: 'Zarr-backed analysis: one process, many threads'
io_access_pattern: auto
io_compression_level: null
io_format: null
io_storage_location: auto
io_target_chunk_mb:
- 128
- 512
max_mem_gb: null
max_workers: null
memory_pause: 0.92
memory_spill: 0.85
memory_target: 0.75
memory_terminate: 0.98
min_workers: null
name: zarr_pipeline
reserve_mem_gb: 20.0
silence_logs: false
spill_compression: auto
spill_threads: null
suggest_chunks: false
tags:
- zarr
- io
temp_base_dir: null
workload_type: io
created_at: '2026-10-01T02:41:05.853649'
modified_at: '2026-10-01T02:41:05.853649'
name: zarr_pipeline
version: '1.7'
Known profiles: ['climate_analysis', 'development', 'interactive', 'production', 'zarr_io_heavy', 'zarr_pipeline']
5. Profile Performance Comparison
Benchmark several profiles on the same workload to pick the best fit.
profiles_to_test = ["development", "interactive"]
times = {}
for profile_name in profiles_to_test:
try:
client, cluster, _ = setup_dask_client(profile=profile_name, dashboard=False)
t0 = time.perf_counter()
x = da.random.random((2000, 2000), chunks=(500, 500))
_ = (x + x.T).mean().compute()
times[profile_name] = time.perf_counter() - t0
client.close(); cluster.close()
print(f"{profile_name:20s} {times[profile_name]:.2f}s")
except Exception as e:
print(f"{profile_name:20s} ERROR: {e}")
times[profile_name] = float("inf")
if times:
fastest = min(times, key=times.get)
print(f"\nFastest: {fastest} ({times[fastest]:.2f}s)")
[setup_dask_client] Configuration summary
temp/spill dir: /tmp/dask-2426
Workers: 2 | threads/worker: 2 | processes: True
Memory: total ~15.6 GiB | usable ~7.6 GiB | per-worker ~3.8 GiB
Compression: spill=auto | comm=False
development 0.42s
interactive ERROR: ❌ Insufficient memory for configuration:
- Required: 1.0 GB
- Available: 0.0 GB
- Shortfall: 1.0 GB
💡 Suggestions:
1. Reduce reserve_mem_gb from 20.0 GB to at most 14.6 GB
2. Request a larger memory allocation for your job
Fastest: development (0.42s)
Configuration Best Practices
Priority order (highest wins): explicit function arguments → profile= or config= → built-in defaults.
An argument you pass always wins, even when it equals the default.
Profile selection guide:
Profile |
Workload |
When to use |
|---|---|---|
|
cpu |
Large xarray reductions on HPC; NetCDF/HDF5 input |
|
io |
Zarr stores and cloud object storage (not NetCDF — its reads serialise on a lock, so use |
|
mixed |
Jupyter notebooks on a login node |
|
mixed |
Testing on a laptop or small VM |
|
mixed |
Long-running automated jobs |
YAML profile tips:
Keep profiles in version control alongside your analysis scripts
Put settings under
config:and use thedescriptionandtagsfields there; YAML comments work for longer notesValidate with
dask-setup validate <name>before deployingUse
based_on: parent_profileto inherit and override a base profile
Memory tuning:
# Conservative — for shared systems or laptops
DaskSetupConfig(reserve_mem_gb=4.0, memory_target=0.60, memory_spill=0.70)
# Aggressive — large dedicated HPC node
DaskSetupConfig(reserve_mem_gb=8.0, memory_target=0.80, spill_compression="lz4") # needs `pip install lz4`