Skip to main content

A simple yet efficient scaling agent for Python apps on Heroku

Project description

Dynoscale Agent

Simple yet efficient scaling agent for Python apps on Heroku

Dynoscale Agent supports both WSGI and ASGI based apps and RQ workers (DjangoQ and Celery support is coming soon). The easies way to use it in your project is import the included Gunicorn hook in your gunicorn.conf.py but we'll explain the setup process in more detail below.

Note that for auto-scaling to work, your web/workers have to run on Standard or Performace dynos!

Getting started

There are generally 3 steps to set up autoscaling with Dynoscale:

  1. Add Dynoscale addon to your Heroku app
  2. Install dynoscale package
  3. Initialize dynoscale when you app starts

1) Enabling Dynoscale add-on

There are two ways to add the Dynoscale add-on to your app. Either you can add it through the broser from Heroku dashboard by navigating to your app, then selecting the resources tab and finally searching for Dynoscale then select your plan and at this point your app will be restarted.

2) Installing dynoscale agent package

This is same as installing any other Python package, for example: python -m pip install dynoscale.

If you'd like to confirm it's installed by heroku, then run:

heroku run python -c "import dynoscale; print(dynoscale.__version__)"  

which will print out the installed version (for example: 1.1.3)

If you'd like to confirm that dynoscale found the right env vars run:

heroku run python -c "from dynoscale.config import Config; print(Config())"

and you'll likely see something like this:

Running python -c "from dynoscale.config import Config; print(Config())" on ⬢ your-app-name-here... up, run.9816 (Eco)
{"DYNO": "run.9816", "DYNOSCALE_DEV_MODE": false, "DYNOSCALE_URL": "https://dynoscale.net/api/v1/report/yoursecretdynoscalehash", "redis_urls": {"REDISCLOUD_URL": "redis://default:anothersecrethere@redis-12345.c258.us-east-1-4.ec2.cloud.redislabs.com:12345"}}

3) Initialize dynoscale during the app startup

This can take multiple forms and depends on your app. Is your app WSGI or ASGI? How do you serve it? Do you have workers?

If you have a WSGI app (ex.: Bottle, Flask, CherryPy, Pylons, Django, ...) and you serve the app with Gunicorn then in your gunicorn.conf.py just import the pre_request hook from dynoscale and that's it:

# `gunicorn.conf.py` - Using Dynoscale Gunicorn Hook
from dynoscale.hooks.gunicorn import pre_request  # noqa # pylint: disable=unused-import

Or if you prefer you can instead pass your WSGI app into DynoscaleWsgiApp():

# `web.py` - Flask Example
from dynoscale.wsgi import DynoscaleWsgiApp

app = Flask(__name__)
app.wsgi_app = DynoscaleWsgiApp(app.wsgi_app)

If you have an ASGI app (ex.: Starlette, Responder, FastAPI, Sanic, Django, Guillotina, ...) pass your ASGI app into DynoscaleASGIApp:

# `web.py` - Starlette Example
import os

from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Route

from dynoscale.asgi import DynoscaleAsgiApp


async def home(_):
    return Response("Hello from Starlette, scaled by Dynoscale!", media_type='text/plain')


app = DynoscaleAsgiApp(Starlette(debug=True, routes=[Route('/', endpoint=home, methods=['GET'])]))

if __name__ == "__main__":
    import uvicorn

    uvicorn.run('web:app', host='0.0.0.0', port=int(os.getenv('PORT', '8000')), log_level="info")

📖 More info on usage

  1. Add dynoscale to your app on Heroku: heroku addons:create dscale
  2. Install dynoscale: python -m pip install dynoscale
    1. Add dynoscale to your app, you can either wrap your app or if you use Gunicorn, you can also just use one of its hooks (pre_request):
      1. If you want to wrap you app (let's look at Flask example):
      import os
      
      from flask import Flask
      
      app = Flask(__name__)
      
      @app.route("/")
      def index():
          return "Hello from Flask!"
      
      if __name__ == "__main__":
          app.run(host='0.0.0.0', port=int(os.getenv('PORT', '8000')), debug=True)
      
      then just wrap your WSGI app like this
      from flask import Flask
      # FIRST, IMPORT DYNOSCALE
      from dynoscale.wsgi import DynoscaleWsgiApp
      
      app = Flask(__name__)
      
      @app.route("/")
      def index():
          return "Hello from Flask!"
      
      if __name__ == "__main__":
          # THE CHANGE BELOW IS ALL YOU NEED TO DO
          app.wsgi_app = DynoscaleWsgiApp(app.wsgi_app)
          # YUP, WE KNOW, CAN'T GET SIMPLER THAN THAT :)
          app.run(host='127.0.0.1', port=3000, debug=True)
      
    2. Or, if you'd prefer to use the hook, then change your gunicorn.conf.py accordingly instead:
      # This one line will do it for you:
      from dynoscale.hooks.gunicorn import pre_request  # noqa # pylint: disable=unused-import
      
      If you already use the pre_request hook, alias ours and call it manually:
      # Alias the import...
      from dynoscale.hooks.gunicorn import pre_request as hook
      
      # ...and remember to call ours first!
      def pre_request(worker, req):
         hook(worker, req)
         # ...do your own thing...
      
  3. Profit! Literally, this will save you money! 💰💰💰 😏

ℹ️ Info

You should consider the dynoscale.wsgi.DynoscaleWsgiApp(wsgi_app) and dynoscale.hooks.gunicorn.pre_request(worker, req) the only two bits of public interface.

🤯 Examples

Please check out ./examples, yes, we do have examples in the repository :)

👩‍💻 Contributing

Install development requirements:

  • If you use Zsh: noglob pip install -e .[test]
  • If you use Bash: pip install -e .[test]

You can run pytest from terminal: pytest

You can run flake8 from terminal: flake8 ./src

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dynoscale-1.1.3.tar.gz (18.1 kB view details)

Uploaded Source

Built Distribution

dynoscale-1.1.3-py3-none-any.whl (19.5 kB view details)

Uploaded Python 3

File details

Details for the file dynoscale-1.1.3.tar.gz.

File metadata

  • Download URL: dynoscale-1.1.3.tar.gz
  • Upload date:
  • Size: 18.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.7.16

File hashes

Hashes for dynoscale-1.1.3.tar.gz
Algorithm Hash digest
SHA256 8b9fb1bee8573f0eef6ab5f20bd034f83f61ba994ecb51902728ae94eb735c09
MD5 067c23a8b9038714396a310f42946719
BLAKE2b-256 d12b8755c2600f6534140a51671eacd8bd5c9a9451042a9b96bd76fcb6453813

See more details on using hashes here.

File details

Details for the file dynoscale-1.1.3-py3-none-any.whl.

File metadata

  • Download URL: dynoscale-1.1.3-py3-none-any.whl
  • Upload date:
  • Size: 19.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.7.16

File hashes

Hashes for dynoscale-1.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 694b8d4eafb2a16ab99ff45984d028e6b084a3f5ef5b7153467c379a4707ab8e
MD5 f719c4b723cb20ba0e08e4081fc88fef
BLAKE2b-256 e088aff42bb6e2508e8585172da5a7a9fcc63fceaa2ec86c4aff4dd129ad1791

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page