Slide 1

Slide 1 text

Lightning Fast Deployment of
 Your Rails-Backed Javascript App Luke Melia, Yapp Labs RailsConf Chicago April 22nd, 2014 1

Slide 2

Slide 2 text

About this Rubyist 2

Slide 3

Slide 3 text

Based in New York & Seattle 3 Yapp Labs ! Ember.js Consulting & Training

Slide 4

Slide 4 text

4 Deploying my app was driving me nuts!

Slide 5

Slide 5 text

5 Javascript App JSON API T&Cs page Home page Our app consisted of:

Slide 6

Slide 6 text

6 Our app consisted of: Javascript App JSON API T&Cs page Home page

Slide 7

Slide 7 text

7 Javascript App JSON API T&Cs page Home page Got changes?
 Build and deploy everything!

Slide 8

Slide 8 text

Q: How long
 does it take
 to deploy a Rails app? 8

Slide 9

Slide 9 text

Install dependencies. Boot app. A: It takes at least a few minutes
 to deploy a Rails app. 9 Transfer lots of files.

Slide 10

Slide 10 text

10 Javascript App JSON API T&Cs page Home page I went days without deploying anything but Javascript changes.

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

14

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

19 Initial request Request: /index.html Response: text/html

Slide 20

Slide 20 text

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:


Slide 21

Slide 21 text

21 Page is parsed, and then a short time later… Request: /assets/app-abc123.js Response: text/javascript

Slide 22

Slide 22 text

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:


Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

24

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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?

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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?

Slide 33

Slide 33 text

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.

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Deploy into redis. Serve out of redis via Rails controller. 36

Slide 37

Slide 37 text

Deploy into redis. Serve out of redis via Rails controller. 37

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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)

Slide 45

Slide 45 text

Possibilities Emerge 45

Slide 46

Slide 46 text

46 Preview

Slide 47

Slide 47 text

➜ 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', '

Slide 48

Slide 48 text

➜ 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', '

Slide 49

Slide 49 text

➜ 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', '

Slide 50

Slide 50 text

➜ 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', '

Slide 51

Slide 51 text

Preview: HTML goes into key based on manifest. “current” key points to that key. 51

Slide 52

Slide 52 text

Preview: HTML goes into key based on manifest. “current” key points to that key. 52

Slide 53

Slide 53 text

53 Dynamic
 HTML Rewriting

Slide 54

Slide 54 text

Dynamic HTML Rewriting As the HTML content passes through the controller, we have the opportunity to make adjustments. 54

Slide 55

Slide 55 text

55 Dynamic HTML Rewriting: Controller Example

Slide 56

Slide 56 text

Dynamic HTML Rewriting:
 Other Use Cases Adding CSRF tokens Including dynamic analytics params Embedding dynamic configuration
 (e.g. feature flags) 56

Slide 57

Slide 57 text

57 A/B Testing

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

59 A/B Testing: Controller Example 1

Slide 60

Slide 60 text

60 A/B Testing: Controller Example 2

Slide 61

Slide 61 text

Possibilities Emerge 61

Slide 62

Slide 62 text

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.

Slide 63

Slide 63 text

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