Slide 1

Slide 1 text

Heroku Under the Hood workshop

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Performance tuning

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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?

Slide 11

Slide 11 text

Buildpacks

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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.

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

app.json, 
 Heroku buttons, 
 and Pipelines

Slide 19

Slide 19 text

$ 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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Pipelines / PR apps Live demo (yipes)

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

>>> 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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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()

Slide 27

Slide 27 text

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