$30 off During Our Annual Pro Sale. View Details »

Heroku Under The Hood - Django Under The Hood 2015

Heroku Under The Hood - Django Under The Hood 2015

Jacob Kaplan-Moss

November 07, 2015
Tweet

More Decks by Jacob Kaplan-Moss

Other Decks in Technology

Transcript

  1. Heroku Under the Hood
    workshop

    View Slide

  2. Agenda
    1. Performance tuning and dyno sizing
    2. Buildpacks
    3. app.json and the Heroku button
    4. Platform API

    View Slide

  3. Performance tuning

    View Slide

  4. Performance basics
    Which WSGI server should I use?
    Recommendations: gunicorn, uWSGI
    Worth trying: waitress, twisted
    Not even once: runserver
    How many workers should I run?
    Start with WEB_CONCURRENCY
    Tune appropriately (more on this later)
    How should I serve static assets?
    Small scale: use Whitenoise
    Large scale, option A: S3 + CDN
    Large scale, option B: Whitenoise + CDN

    View Slide

  5. Dyno types
    Type RAM Compute Cost
    Free / Hobby 512 MB 1-4x $0 / $7
    Standard-1X 512 MB 1-4x $25
    Standard-2X 1 GB 2x - 4x $50
    Performance-M 2.5 GB 12x $250
    Performance-L 14 GB 50x $500

    View Slide

  6. Optimizing dyno types
    Assumption: your application is RAM-bound.
    Recommendation: optimize for cost per process.
    You’ll need to measure:
    RAM per process
    number of required processes
    Math:
    cost_per_process = (dyno_ram / process_ram) * dyno_cost
    H/T: https://medium.com/swlh/running-a-high-traffic-rails-app-on-heroku-s-performance-dynos-d9e6833d34c

    View Slide

  7. Example 1
    Dyno
    Processes /
    dyno
    Required #
    of dynos
    Total cost Cost / process
    1X 4 5 $125 $6
    2X 8 3 $150 $8
    P-M 21 1 $250 $13
    P-L 119 1 $500 $25
    RAM per process: 120
    Total processes: 20

    View Slide

  8. Example 2
    Dyno
    Processes /
    dyno
    Required #
    of dynos
    Total cost Cost / process
    1X 1 60 $1,500 $25
    2X 2 30 $1,500 $25
    P-M 5 12 $3,000 $50
    P-L 31 2 $1,000 $17
    RAM per process: 450
    Total processes: 60

    View Slide

  9. Making measurements
    1. Observe memory performance:
    log-runtime-metrics
    Heroku Dashboard
    Librato
    2. Load test
    Siege 

    https://www.joedog.org/siege-home/
    Bees with Machine Guns 

    https://github.com/newsapps/beeswithmachineguns
    Load testing addons 

    https://elements.heroku.com/addons#testing

    View Slide

  10. Exercise 1:
    performance tuning
    A. What’s your RAM per process?
    B. What’s your number of required processes?
    C. What’s your optimal dyno formation?

    View Slide

  11. Buildpacks

    View Slide

  12. $ git push heroku master
    ...
    -----> Python app detected
    -----> Installing runtime (python-2.7.10)
    -----> Installing dependencies using pip
    ...
    What’s a buildpack?

    View Slide

  13. How buildpacks work
    bin/detect — will this buildpack build this app?
    bin/compile — compile source into app
    bin/release — emit default config/addons

    View Slide

  14. Python buildpack
    https://github.com/heroku/heroku-buildpack-python

    View Slide

  15. Other interesting buildpacks
    Conda
    https://github.com/kennethreitz/conda-buildpack
    Null
    https://github.com/ryandotsmith/null-buildpack
    pgbouncer
    https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-pgbouncer
    nginx
    https://elements.heroku.com/buildpacks/ryandotsmith/nginx-buildpack

    View Slide

  16. Multiple buildpacks
    Use cases:
    nginx / pgbouncer
    asset collection via Gulp/Grunt/etc.
    …?
    $ heroku buildpacks
    === aqueous-shore-5792 Buildpack URL
    heroku/python
    $ heroku buildpacks:add --index 2 heroku/nodejs
    Buildpack added. Next release on aqueous-shore-5792 will use:
    1. heroku/python
    2. heroku/nodejs
    Run `git push heroku master` to create a new release using these buildpacks.

    View Slide

  17. Exercise 2:
    try out multi-buildpack
    A. Try out nginx or pgbouncer. 

    Do they improve performance?
    B. Try a Node asset manager:

    https://github.com/beaugunderson/django-gulp

    View Slide

  18. app.json, 

    Heroku buttons, 

    and Pipelines

    View Slide

  19. $ cat app.json
    {
    "name": "Appointment Reminders (Django)",
    "description": "Appointment Reminders in Django with Twilio",
    "repository": "https://github.com/atbaker/appointment-reminders-django",
    "addons": [
    "heroku-postgresql:hobby-dev",
    "redistogo:nano"
    ],
    "env": {
    "TWILIO_ACCOUNT_SID": {
    "description": "Your Twilio account secret ID",
    "value": "enter_your_account_sid_here",
    "required": true
    },
    ...
    }
    }
    app.json

    View Slide

  20. [![Deploy](https://www.herokucdn.com/deploy/
    button.png)](https://heroku.com/deploy?
    template=https://github.com/atbaker/appointment-
    reminders-django)
    template=https://github.com/atbaker/appointment-
    reminders-django">
    button.png">

    Heroku button

    View Slide

  21. Pipelines / PR apps
    Live demo
    (yipes)

    View Slide

  22. Exercise 3:
    app.json / PR apps
    A. Create an app.json (and Heroku button?) for your app.
    B. Try out pull request apps.

    View Slide

  23. Platform API
    https://devcenter.heroku.com/articles/platform-api-quickstart
    https://devcenter.heroku.com/articles/platform-api-reference

    View Slide

  24. >>> import netrc, requests, json
    >>> token = netrc.netrc().hosts['api.heroku.com'][2]
    >>> h = requests.session()
    >>> h.headers['Authorization'] = 'Bearer ' + token
    >>> h.headers['Accept'] = 'application/vnd.heroku+json; version=3'
    >>> h.headers['Content-Type'] = 'application/json'
    >>> formation = h.get('https://api.heroku.com/apps/NAME/formation').json()
    >>> {f['type']: f['quantity'] for f in formation}
    {'web': 1, 'worker': 0}
    >>> payload = {'quantity': 4, 'size': 'standard-1X'}
    >>> h.patch('https://api.heroku.com/apps/NAME/formation/web',
    data=json.dumps(payload))
    >>> formation = h.get('https://api.heroku.com/apps/NAME/formation').json()
    >>> {f['type']: f['quantity'] for f in formation}
    {'web': 4, 'worker': 0}
    Intro

    View Slide

  25. Authentication
    Authentication uses bearer tokens:
    For local dev and/or fooling around, reading a token from .netrc is fine.
    Better: direct authentication with token exchange:
    Best: proper use of OAuth + scopes: 

    https://devcenter.heroku.com/articles/oauth
    >>> h.post('https://api.heroku.com/oauth/authorizations',
    ... data=json.dumps({'description': 'demo auth'})).json()
    {'access_token': {'expires_in': None,
    'id': '4d6e04f0-693b-4118-bb9b-646e723ff7fa',
    'token': ‘4a1637b4-9bc8-4ad9-8747-43ec7c744621'},
    ...}
    >>> h.headers['Authorization'] = 'Bearer ' + token

    View Slide

  26. Build & Slug APIs
    Deploy to Heroku without git push heroku master!
    To build without releasing, use a build app and copy slugs:

    https://devcenter.heroku.com/articles/platform-api-copying-slugs
    You can also create slugs from scratch: 

    https://devcenter.heroku.com/articles/platform-api-deploying-slugs
    >>> source = h.post('https://api.heroku.com/apps/aqueous-shore-5792/sources').json()
    >>> requests.put(source['source_blob']['put_url'], data=open('foo.tgz').read())
    >>> payload = {'source_blob': {'url': source['source_blob']['get_url'],
    ... 'version': 'abcd1234'}}
    >>> build = h.post('https://api.heroku.com/apps/aqueous-shore-5792//builds', 

    data=json.dumps(payload)).json()

    View Slide

  27. Exercise 4:
    platform API
    A. Build an auto-scaler.
    B. Build an alternate deploy flow.
    C. Re-create pull-request apps (!)

    View Slide