Productify Autoscaler — Nomadscaler
Nomad strategy plugin for the Productify autoscaler. This component is implemented in Go and is intended to run as a container or a Nomad job.
Overview
The plugin:
- Interfaces with the Nomad Autoscaler as a strategy plugin
- Queries the Optimizer service for predictions
- Implements a second-level caching mechanism
- Returns desired replica counts, which the Nomad Autoscaler applies to the job
Installation
Build from Source
cd nomadscaler
./build.shThe compiled binary will be in ./bin/productify-scaler. The script also builds a productify/scaler:local Docker image — the Nomad Autoscaler with the plugin preinstalled in /plugins.
Run Locally
docker run --rm ghcr.io/productifyfw/autoscaler-nomadscaler:latestConfiguration
Configuration for nomadscaler is managed via HCL configuration. An example job that renders the config.hcl is available at:
nomadscaler/config/autoscaler.hcl
Strategy Configuration Parameters
The productify-scaler strategy plugin supports the following configuration parameters:
optimizer_token(required): Authentication token for the optimizer APIoptimizer_url(required): URL of the optimizer servicemin(required): Minimum number of replicasmax(required): Maximum number of replicasmetric_app_name(required): Name of the application for metricscache_size(optional, default: 10): Number of desired replica values to cache from optimizer response
Cache Behavior
The plugin uses a second-level time-based cache for accurate scaling decisions:
- Optimizer predictions: The optimizer forecasts demand at second-level granularity and returns desired replica counts for the next N seconds (where N =
cache_size) - Cache storage: All returned values are cached with a timestamp
- Value selection: Each second corresponds to one cache position. The plugin selects the value matching elapsed seconds since cache update
- Cache exhaustion: If more time has elapsed than cache size, the last cached value is reused
- Refresh strategy: After using the cache 2 times, the plugin attempts a fresh optimizer request
- Fallback: If the optimizer request fails, the plugin continues using cached values
Example with cache_size=10:
Time (s) | 0 1 2 3 4 5 6 7 8 9 | 10+
Value | 3 3 4 4 5 5 6 6 7 7 | 7 (last)
Request | ^ - - F - - F - - - | F
| F=Fresh request, -=Use cacheThis design ensures smooth scaling transitions while maintaining responsiveness to changing demand patterns.
Scaling Logic
On every policy check evaluation, the plugin:
- Reads the strategy configuration (
optimizer_token,optimizer_url,min,max,metric_app_name,cache_size), falling back to the agent-level plugin config for the token and URL - Decides whether to serve from the cache or request fresh predictions (a fresh request is made when the cache is empty or has been used at least twice)
- On a fresh request, sends
POST /optimizeto the optimizer (5-second timeout) with the check details, current/min/max replicas and the collected metric samples - Caches the returned
desiredarray and selects the value for the elapsed second; on request failure, it falls back to the cached values - Compares the selected count to the current count and returns the scaling direction and desired count to the Nomad Autoscaler
Metrics Collection
Nomad APM
Use Nomad's built-in metrics:
check "cpu" {
source = "nomad-apm"
query = "avg_cpu_percent"
}
check "memory" {
source = "nomad-apm"
query = "avg_memory_percent"
}Prometheus
Query Prometheus metrics:
check "cpu" {
source = "prometheus"
query = "avg(rate(container_cpu_usage_seconds_total[5m]))"
}Troubleshooting
Plugin Not Loading
Check:
- Plugin binary is in correct directory
- Binary has execute permissions
- Nomad Autoscaler logs for errors
tail -f /var/log/nomad-autoscaler.log | grep productify-scalerOptimizer Unreachable
Verify:
- Optimizer service is running
- URL is correct and accessible
- Network connectivity
- Firewall rules
Test connection:
curl http://{ip}:{optimizer_port}/healthNo Scaling Actions
Debug:
- Check if policy is enabled
- Verify metrics are being collected
- Review cooldown period
- Check min/max constraints
Enable debug logging:
log_level = "DEBUG"