Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lightning Fast Deployment of Your Rails-Backed ...

Lightning Fast Deployment of Your Rails-Backed Javascript App

Are you restarting your Rails server every time you deploy JavaScript? Are you waiting 5 minutes or more to deploy static JavaScript? Stop! We were able to cut our JavaScript front-end deployment times from more than 5 minutes to less than 15 seconds with zero downtime. As a bonus, we can preview new releases on production before making them live. I will share all the details of an approach you can implement on your Rails-backed Javascript app to make deploying JavaScript updates a joy. Video: https://www.youtube.com/watch?v=QZVYP3cPcWQ

Luke Melia

April 22, 2014
Tweet

More Decks by Luke Melia

Other Decks in Technology

Transcript

  1. Based in New York & Seattle 3 Yapp Labs !

    Ember.js Consulting & Training
  2. 7 Javascript App JSON API T&Cs page Home page Got

    changes?
 Build and deploy everything!
  3. Install dependencies. Boot app. A: It takes at least a

    few minutes
 to deploy a Rails app. 9 Transfer lots of files.
  4. 10 Javascript App JSON API T&Cs page Home page I

    went days without deploying anything but Javascript changes.
  5. 11 Javascript App JSON API T&Cs page Home page And

    waiting 5 minutes each time
 I deployed static JS changes!
  6. 12 Javascript App JSON API T&Cs page Home page I

    wasn’t just annoying myself. Our users had “hiccups” each deploy.
  7. 13 Javascript App JSON API T&Cs page Home page I

    wasn’t just annoying myself. Our users had “hiccups? each deploy.
  8. Downtime and other hiccups
 during deploys If your Rails app

    takes several seconds to boot,
 no requests are getting served during that time. Under high load and most architectures, those waiting requests are queuing up, one behind the other. End result: users can experience your site as non- responsive / down while you are deploying and
 a few seconds after. 15
  9. Downtime and other hiccups
 during deploys Heroku has an experimental

    solution: heroku labs:enable preboot Starts up new servers (dynos) and then switches traffic over after 3 minutes 16
  10. Downtime and other hiccups
 during deploys Puma and Unicorn have

    facilities to restart one worker at a time via signals sent to the master process. HAProxy is another tool that can be useful. Out of scope: zero-downtime migrations (find me later) 17
  11. Downtime and other hiccups
 during deploys Issues with static assets

    and achieving zero-downtime deploys are not often discussed. So let’s discuss them. 18
  12. 20 Initial request Request: /index.html Response: text/html Asset files are

    typically “fingerprinted” and served with fingerprint-based filenames and far future expires headers.
 So this HTML response might contain:
 <script src="/assets/app-abc123.js"></src>
  13. 21 Page is parsed, and then a short time later…

    Request: /assets/app-abc123.js Response: text/javascript
  14. 22 But during deployments, this can break down index.html /assets/app-abc123.js

    index.html /assets/app-def456.js Request: /index.html Response: text/html HTML response contains:
 <script src="/assets/app-abc123.js"></src>
  15. 23 …when traffic starts routing to the new app index.html

    /assets/app-abc123.js index.html /assets/app-def456.js Request: /assets/app-abc123.js Response: 404 Not Found
  16. Hiccup-free: thinking about keeping static assets working during deploys Both

    old and new versions of assets need to be available for at least a few minutes during a deploy. 25
  17. Hiccup-free: thinking about keeping static assets working during deploys We

    could figure out how to do this on our app servers, or move assets elsewhere… 26
  18. Hiccup-free: thinking about keeping static assets working during deploys If

    our static assets aren’t served off of our app servers, we don’t need to deploy asset-only changes there, right? 27
  19. 28 Sketching out the idea Static assets server Rails server

    Deploy Rails
 app code Dev or CI Deploy
 JS, CSS, images What about the
 HTML page?
  20. Deployment & serving strategy:
 factors to consider about HTML page

    Points to fingerprinted JS/CSS but is not fingerprinted itself Contains JS URLs and code to boot JS app and load CSS in the right order Good place to provide environment-specific configuration to Javascript 29
  21. Deployment & serving strategy:
 factors to consider about HTML page

    When on the same domain as API, avoids CORS complexity Caching should be minimal to none in order to allow for updates that take effect quickly 30
  22. Conclusion about deploying and serving HTML page HTML Page should

    be managed and deployed as part of static asset deployment process HTML Page should be served by Rails, but updates should not require re-deploying the Rails app or restarting the Rails server 31
  23. 32 Sketching out the idea, II Static assets server Rails

    server Deploy Rails
 app code Dev or CI Deploy
 JS, CSS, images API requests dynamic Rails pages HTML for JS App JS for JS App CSS, Images for JS App Deploy HTML?
  24. 33 Sketching out the idea, II Rails servers Dev or

    CI Deploy HTML Deploy HTML to filesystem of each server? No, because disk is ephemeral in many deployment environments.
  25. 34 Sketching out the idea, II Rails servers Dev or

    CI Deploy HTML Deploy HTML to S3 and read from Rails servers? Better, but S3 reads can be slow and we want this page fast. Read
  26. 35 Sketching out the idea, II Rails servers Dev or

    CI Deploy HTML Deploy HTML to Redis and read from Rails servers? Persistent, fast, and already in my environment. Yes! Read
  27. 38 Refining the approach Static assets server Rails server Deploy

    Rails
 app code Dev or CI Deploy
 JS, CSS, images API requests dynamic Rails pages HTML for JS App JS for JS App CSS, Images for JS App Deploy HTML
  28. 39 Refining the approach Static assets server (AWS S3) Rails

    server Dev or CI Deploy
 JS, CSS, images (additive) API requests dynamic Rails pages HTML for JS App JS for JS App CSS, Images for JS App Deploy HTML AWS Cloudfront Deploy Rails
 app code
  29. Differential, additive deploy to S3 S3 can be slow for

    getting a list of files Instead, generate a manifest file of current assets. Compare them against the remote manifest and upload only what is missing. Then update the remote manifest with the difference. (Leave purging as TODO.) https://github.com/yappbox/embarista/blob/master/lib/ embarista/s3sync.rb 40
  30. Repository management This architecture paves the way to manage your

    Javascript app in one SCM repository, and your Rails app in the other. Each can be deployed and versioned independently. Thinking of your Javascript app as a independent client of your API works well! 41
  31. Build tools for your Javascript app With separate repositories and

    deployment paths, you are free to choose best of breed build tooling for your Javascript app. Javascript build tools are seeing the most attention and innovation right now. 42
  32. 43 Refining the approach Static assets server (AWS S3) Rails

    server Dev or CI Deploy
 JS, CSS, images (additive) API requests dynamic Rails pages HTML for JS App JS for JS App CSS, Images for JS App Deploy HTML AWS Cloudfront Deploy Rails
 app code
  33. 44 How fast is this in the real world? Xfer

    HTML 2.38s Xfer Assets 1.01s Build 6.55s 9.94 seconds (real-world example project; your results will be vary mostly on build time)
  34. ➜ cd yapp-prefs; rake dist Build complete. To deploy, run

    rake deploy:assets[b35b97f3] ➜ rake deploy:assets[b35b97f3] yapp-assets -> prefs/yapp-prefs.min-530b21e2.js yapp-assets -> prefs/yapp-prefs.min-ab0d7f5e.css yapp-assets -> prefs-manifest-latest.yml yapp-assets -> b35b97f3.yml YAPP_ENV=qa|prod rake deploy:generate_index[b35b97f3] ➜ YAPP_ENV=qa rake deploy:generate_index[b35b97f3] redis.set('prefs:index:b35b97f3', '<!DOCTYPE...') To preview: https://www.yappqa.us/prefs/?manifest_id=b35b97f3 To activate: YAPP_ENV=qa rake "deploy:set_current_index[b35b97f3]" ➜ YAPP_ENV=qa rake "deploy:set_current_index[b35b97f3]" redis.set('prefs:index:current', 'b35b97f3') 47 Preview: sample deployment session Build
  35. ➜ cd yapp-prefs; rake dist Build complete. To deploy, run

    rake deploy:assets[b35b97f3] ➜ rake deploy:assets[b35b97f3] yapp-assets -> prefs/yapp-prefs.min-530b21e2.js yapp-assets -> prefs/yapp-prefs.min-ab0d7f5e.css yapp-assets -> prefs-manifest-latest.yml yapp-assets -> b35b97f3.yml YAPP_ENV=qa|prod rake deploy:generate_index[b35b97f3] ➜ YAPP_ENV=qa rake deploy:generate_index[b35b97f3] redis.set('prefs:index:b35b97f3', '<!DOCTYPE...') To preview: https://www.yappqa.us/prefs/?manifest_id=b35b97f3 To activate: YAPP_ENV=qa rake "deploy:set_current_index[b35b97f3]" ➜ YAPP_ENV=qa rake "deploy:set_current_index[b35b97f3]" redis.set('prefs:index:current', 'b35b97f3') 48 Preview: sample deployment session Upload assets to S3
  36. ➜ cd yapp-prefs; rake dist Build complete. To deploy, run

    rake deploy:assets[b35b97f3] ➜ rake deploy:assets[b35b97f3] yapp-assets -> prefs/yapp-prefs.min-530b21e2.js yapp-assets -> prefs/yapp-prefs.min-ab0d7f5e.css yapp-assets -> prefs-manifest-latest.yml yapp-assets -> b35b97f3.yml YAPP_ENV=qa|prod rake deploy:generate_index[b35b97f3] ➜ YAPP_ENV=qa rake deploy:generate_index[b35b97f3] redis.set('prefs:index:b35b97f3', '<!DOCTYPE...') To preview: https://www.yappqa.us/prefs/?manifest_id=b35b97f3 To activate: YAPP_ENV=qa rake "deploy:set_current_index[b35b97f3]" ➜ YAPP_ENV=qa rake "deploy:set_current_index[b35b97f3]" redis.set('prefs:index:current', 'b35b97f3') 49 Preview: sample deployment session Add HTML
 to redis
  37. ➜ cd yapp-prefs; rake dist Build complete. To deploy, run

    rake deploy:assets[b35b97f3] ➜ rake deploy:assets[b35b97f3] yapp-assets -> prefs/yapp-prefs.min-530b21e2.js yapp-assets -> prefs/yapp-prefs.min-ab0d7f5e.css yapp-assets -> prefs-manifest-latest.yml yapp-assets -> b35b97f3.yml YAPP_ENV=qa|prod rake deploy:generate_index[b35b97f3] ➜ YAPP_ENV=qa rake deploy:generate_index[b35b97f3] redis.set('prefs:index:b35b97f3', '<!DOCTYPE...') To preview: https://www.yappqa.us/prefs/?manifest_id=b35b97f3 To activate: YAPP_ENV=qa rake "deploy:set_current_index[b35b97f3]" ➜ YAPP_ENV=qa rake "deploy:set_current_index[b35b97f3]" redis.set('prefs:index:current', 'b35b97f3') 50 Preview: sample deployment session Activate
 redis key
  38. Dynamic HTML Rewriting As the HTML content passes through the

    controller, we have the opportunity to make adjustments. 54
  39. Dynamic HTML Rewriting:
 Other Use Cases Adding CSRF tokens Including

    dynamic analytics params Embedding dynamic configuration
 (e.g. feature flags) 56
  40. A/B Testing I’ve experimented with two types of
 A/B testing

    Setting global flags based on A/B bucket Serving up wholly different HTML based on A/B bucket 58
  41. Thanks to my @YappLabs colleagues who helped create this approach:


    Kris Selden Stefan Penner Ray Cohen 62 We got ideas about this from rumors we heard about Square using a similar approach. So thank you, nameless Square engineers.
  42. Q&A Some examples appear courtesy of my company.
 Yapp Labs

    offers Ember.js consulting and training. Creative Commons photo credits: https://www.flickr.com/photos/atelier_tee/109448791, https://www.flickr.com/photos/napdsp/12124061354 63 Follow me @lukemelia