A fast ECS autoscaler for Celery workloads.
Project description
celery-ecs-autoscaler
Queue-depth-driven autoscaler for Celery worker fleets running on AWS ECS.
Monitors Redis or RabbitMQ queue depths and adjusts ECS service desiredCount to match load — scaling out when queues grow and scaling in when they drain.
Installation
pip install celery-ecs-autoscaler
Requires Python 3.11+. Core dependencies: boto3, redis.
Quick start
Write a small config module and call run():
from celery_ecs_autoscaler import (
AppConfig, EcsServiceTarget, RedisQueueSource, ScalingTarget, run
)
config = AppConfig(
poll_seconds=15,
dry_run=False,
targets=[
ScalingTarget(
name="default-workers",
source=RedisQueueSource(
broker_url="redis://localhost:6379/0",
queue_names=["celery"],
),
ecs=EcsServiceTarget(
cluster_name="my-cluster",
service_name="celery-workers",
worker_concurrency=8,
min_tasks=1,
max_tasks=20,
),
)
],
)
run(config)
See examples/redis_worker/config.py for a full runnable example.
Configuration reference
AppConfig
| Field | Type | Default | Description |
|---|---|---|---|
poll_seconds |
int |
15 |
How often to poll queues and ECS |
dry_run |
bool |
True |
Log decisions without calling update_service |
targets |
list[ScalingTarget] |
required | One or more scaling targets |
IAM permissions required: the process must be able to call
ecs:DescribeServicesandecs:UpdateService. When running on ECS, attach these to the task role. The AWS region is resolved automatically from the task environment — no explicit configuration needed.
EcsServiceTarget
| Field | Type | Default | Description |
|---|---|---|---|
cluster_name |
str |
required | ECS cluster name |
service_name |
str |
required | ECS service name |
worker_concurrency |
int |
required | Celery --concurrency value per task |
min_tasks |
int |
1 |
Minimum desired task count (set 0 for scale-to-zero) |
max_tasks |
int |
20 |
Maximum desired task count |
target_pressure |
float |
0.75 |
Scale out when queued / slots > target_pressure |
scale_in_pressure |
float |
0.25 |
Scale in when queued / slots < scale_in_pressure |
scale_out_step |
int |
2 |
Max tasks to add per poll |
scale_in_step |
int |
1 |
Max tasks to remove per poll |
scale_out_cooldown_seconds |
int |
60 |
Minimum seconds between scale-out actions |
scale_in_cooldown_seconds |
int |
120 |
Minimum seconds between scale-in actions |
Queue sources
RedisQueueSource — reads queue depth using LLEN (Celery default transport):
RedisQueueSource(broker_url="redis://...", queue_names=["celery", "priority"])
RabbitMQQueueSource — reads messages_ready via the RabbitMQ Management HTTP API:
RabbitMQQueueSource(
management_url="http://user:pass@localhost:15672",
queue_names=["celery"],
vhost="/",
)
You can also implement your own source by satisfying the QueueSource Protocol:
class QueueSource(Protocol):
queue_names: list[str]
def get_depth(self) -> int: ...
Scaling algorithm
Each poll:
- Fetch total queue depth across all configured
queue_names. - Fetch current ECS
desiredCount. - Compute
pressure = queued / (desired_tasks * worker_concurrency). - Scale out if
pressure > target_pressure(step-limited, cooldown-gated). - Scale in if
pressure < scale_in_pressure(step-limited, cooldown-gated). - Hold otherwise (deadband between the two thresholds).
Scale-out and scale-in cooldowns are tracked independently per target so a scale-in event does not block an urgent scale-out.
Deployment on ECS
The autoscaler itself runs as a single-instance ECS service. Recommended service settings:
{
"desiredCount": 1,
"deploymentConfiguration": {
"minimumHealthyPercent": 0,
"maximumPercent": 100
}
}
minimumHealthyPercent: 0 allows ECS to stop the old task before starting the new one, avoiding a period where two instances run simultaneously (which would cause duplicate scaling decisions).
All scaling decisions are logged as structured JSON to stdout:
{
"message": "scaling poll",
"target": "default-workers",
"queued": 14,
"action": "scale_out",
"next_tasks": 4,
"pressure": 0.4375,
"updated": true
}
Developing
Prerequisites
- Python 3.11+
- uv
Setup
uv sync
uv run pre-commit install
Running tests
uv run pytest tests/ -v
Linting
Ruff is configured in pyproject.toml with a broad ruleset including security checks (S/bandit), bugbear, import sorting, and pytest style. Pre-commit runs it automatically on every commit.
To run manually:
uv run pre-commit run --all-files
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file celery_ecs_autoscaler-1.0.0.tar.gz.
File metadata
- Download URL: celery_ecs_autoscaler-1.0.0.tar.gz
- Upload date:
- Size: 26.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5b3a115e1397c454d87eda4ab538c5652718d9b2333f03498d84fc6cd19c1c28
|
|
| MD5 |
ac75714caef8f31c5428e15bf5a1e843
|
|
| BLAKE2b-256 |
3b299942e574e17f87856820bc247d5bacd921a04c7466d99aaf537375fda3a3
|
Provenance
The following attestation bundles were made for celery_ecs_autoscaler-1.0.0.tar.gz:
Publisher:
publish.yml on danwiltshire/celery-ecs-autoscaler
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
celery_ecs_autoscaler-1.0.0.tar.gz -
Subject digest:
5b3a115e1397c454d87eda4ab538c5652718d9b2333f03498d84fc6cd19c1c28 - Sigstore transparency entry: 1155355304
- Sigstore integration time:
-
Permalink:
danwiltshire/celery-ecs-autoscaler@719043549de2d629289cd663e97bf55237d1c68a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/danwiltshire
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@719043549de2d629289cd663e97bf55237d1c68a -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file celery_ecs_autoscaler-1.0.0-py3-none-any.whl.
File metadata
- Download URL: celery_ecs_autoscaler-1.0.0-py3-none-any.whl
- Upload date:
- Size: 9.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
693de1b827fc86ac2b72d2dd7b2de6c28ccc4a4506d22fc949ba1d1729f34771
|
|
| MD5 |
e19b925ab339ae4652c5e9884e64b4c6
|
|
| BLAKE2b-256 |
7d3a78529ed62d080239f12b368fbfacafe72c025aeb751596788284258aa7c4
|
Provenance
The following attestation bundles were made for celery_ecs_autoscaler-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on danwiltshire/celery-ecs-autoscaler
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
celery_ecs_autoscaler-1.0.0-py3-none-any.whl -
Subject digest:
693de1b827fc86ac2b72d2dd7b2de6c28ccc4a4506d22fc949ba1d1729f34771 - Sigstore transparency entry: 1155355307
- Sigstore integration time:
-
Permalink:
danwiltshire/celery-ecs-autoscaler@719043549de2d629289cd663e97bf55237d1c68a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/danwiltshire
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@719043549de2d629289cd663e97bf55237d1c68a -
Trigger Event:
workflow_dispatch
-
Statement type: