< Return to Video

RailsConf 2014 - Lightning Fast Deployment of Your Rails-backed JavaScript app

  • 0:18 - 0:19
    LUKE MELIA: All right, folks.
  • 0:19 - 0:24
    Thanks for coming out. So you are at Lightning
    Fast
  • 0:24 - 0:26
    Deployment of Your Rails-Baked Javascript
    APP.
  • 0:26 - 0:29
    Hopefully you're in the right place.
  • 0:29 - 0:32
    My name is Luke. I, Luke Melia. I live
  • 0:32 - 0:36
    in Manhattan in New York City. Got a couple
  • 0:36 - 0:39
    little girls who are learning Ruby and Javascript
    using
  • 0:39 - 0:44
    Code Academy and KidsRuby. And I have a company
  • 0:44 - 0:47
    called Yapp that I co-founded. We're one of
    these
  • 0:47 - 0:51
    kind of hybrid product and consulting companies.
    And when
  • 0:51 - 0:54
    I'm not doing Dad stuff or coding, I really
  • 0:54 - 0:57
    love to play beach volleyball and have recently
    taken
  • 0:57 - 0:59
    up parkour.
  • 0:59 - 1:02
    So Yapp Labs is the consulting side of our
  • 1:02 - 1:04
    business. We do Ember.js consulting and training
    based out
  • 1:04 - 1:08
    of New York and Seattle, if, if that's interesting,
  • 1:08 - 1:10
    happy to talk with you about that.
  • 1:10 - 1:13
    So, by way of introducing this topic, I want
  • 1:13 - 1:16
    to tell you a story. And it's a story
  • 1:16 - 1:22
    of when deployments were driving me crazy.
    You know,
  • 1:22 - 1:25
    kind of like, tear my hair out crazy.
  • 1:25 - 1:27
    We had an app, and the app consisted, you
  • 1:27 - 1:29
    know, it was pretty straightforward for a
    modern app.
  • 1:29 - 1:31
    And it was a Rails app that had a
  • 1:31 - 1:34
    home page. It had your terms and conditions
    page.
  • 1:34 - 1:36
    You can't have a site without that. It had
  • 1:36 - 1:38
    a Javascript app, which in this case was an
  • 1:38 - 1:41
    Ember app, but, you know, you can substitute
    any
  • 1:41 - 1:44
    kind of rich Javascript MVC app that you,
    style
  • 1:44 - 1:47
    you'd like, for the purposes of this talk.
    I
  • 1:47 - 1:49
    then, of course, had a JSON API.
  • 1:49 - 1:51
    And so these, this was kind of the bullet
  • 1:51 - 1:54
    points, but in terms of the amount of code,
  • 1:54 - 1:55
    the complexity, and how much time that it
    took
  • 1:55 - 1:58
    working on it, it was more like this, right.
  • 1:58 - 2:00
    We had a lot of work on the Javascript
  • 2:00 - 2:02
    app. Some, a bunch more in the JSON API.
  • 2:02 - 2:04
    And the rest of the site was, you know,
  • 2:04 - 2:07
    pretty trivial.
  • 2:07 - 2:09
    But in terms of deployments, every time we
    wanted
  • 2:09 - 2:12
    to, every time I made a change and wanted
  • 2:12 - 2:15
    to deploy it, we would package everything
    up and
  • 2:15 - 2:18
    deploy it. And so I have a question for
  • 2:18 - 2:21
    you folks. Hopefully everybody's, in the room
    has worked
  • 2:21 - 2:23
    with Rails. How long does it take to deploy
  • 2:23 - 2:25
    a Rails app? We're gonna do a show of
  • 2:25 - 2:27
    hands, and by the end, I hope everybody will
  • 2:27 - 2:29
    have their Rails app, sorry, will have their
    hands
  • 2:29 - 2:30
    up.
  • 2:30 - 2:32
    And so, please start by raising your hand
    if
  • 2:32 - 2:35
    your Rails app is deployed in less than thirty
  • 2:35 - 2:38
    seconds. OK. Good awesome. One, I want to
    talk
  • 2:38 - 2:42
    with you later. How about less than one minute?
  • 2:42 - 2:47
    Cool. A few folks. Less than three minutes?
    A
  • 2:47 - 2:51
    bunch more. Less than five minutes? Keep,
    keep your
  • 2:51 - 2:52
    hands up even if you were in the early
  • 2:52 - 2:53
    group. Less than five minutes.
  • 2:53 - 2:56
    OK, so we're probably at a majority now. Less
  • 2:56 - 3:02
    than ten minutes? Keep your hands up. OK.
    And
  • 3:02 - 3:04
    less than twenty minutes? I hope that's everybody
    in
  • 3:04 - 3:07
    the room. Please, please, mercy. OK. Cool.
  • 3:07 - 3:10
    So, the, I think the, the answer is, it
  • 3:10 - 3:11
    takes at least a few minutes to deploy a
  • 3:11 - 3:14
    Rails app, unless you're one of an exceptional
    few
  • 3:14 - 3:17
    folks in the audience. And I get it. There's
  • 3:17 - 3:20
    a lot, you know, there's files to transfer.
    There's
  • 3:20 - 3:23
    dependencies to install. Most modern Rails
    apps, you know,
  • 3:23 - 3:25
    that I run into, that we create, have a
  • 3:25 - 3:28
    lot of gem dependencies. It takes some time
    to
  • 3:28 - 3:31
    boot the app with all those dependencies and
    just
  • 3:31 - 3:33
    with, just with your app code.
  • 3:33 - 3:36
    And so, that's fine, except for, I was going
  • 3:36 - 3:39
    days just working on the Javascript app. Right,
    I
  • 3:39 - 3:42
    was just making changes in Javascript, and
    every time
  • 3:42 - 3:45
    I wanted to deploy, I was waiting five minutes
  • 3:45 - 3:51
    in our case to just deploy static Javascript
    changes.
  • 3:51 - 3:52
    And it really made me want to throw something
  • 3:52 - 3:54
    across the room. Why was I doing this to
  • 3:54 - 3:55
    myself?
  • 3:55 - 3:59
    And it wasn't just me that I was annoying.
  • 3:59 - 4:02
    I was also annoying our users, because, in
    most,
  • 4:02 - 4:07
    in most Rails deployment scenarios, there
    are some, there
  • 4:07 - 4:09
    can be some hiccups in each deploy. And we'll,
  • 4:09 - 4:11
    so let's talk a little bit about this, this
  • 4:11 - 4:16
    kind of hiccups and deployments and zero downtime
    deploys.
  • 4:16 - 4:19
    So if your Rails app takes several seconds
    to
  • 4:19 - 4:22
    boot, which is probably about average, obviously
    it can't
  • 4:22 - 4:26
    serve requests during that time. And so, under
    high
  • 4:26 - 4:29
    load in most architectures, most requesters
    are just gonna
  • 4:29 - 4:31
    be queued, waiting for the app to be ready
  • 4:31 - 4:34
    to handle requests. And then once it boots
    up,
  • 4:34 - 4:37
    it's gonna start handling those requests,
    and eventually flush
  • 4:37 - 4:41
    that queue and hopefully catch up to the requests
  • 4:41 - 4:43
    as they're coming in.
  • 4:43 - 4:46
    And so users that are hitting the, hitting
    your
  • 4:46 - 4:49
    app during this time may experience at, at
    best
  • 4:49 - 4:51
    case just a couple of seconds of downtime.
    At
  • 4:51 - 4:53
    worst case, kind of a feeling like that, that
  • 4:53 - 4:55
    this site is not responsive.
  • 4:55 - 4:58
    And so it disappoints me that we don't yet
  • 4:58 - 5:02
    have a kind of conventional solution for zero
    downtime
  • 5:02 - 5:04
    deploys. But it kind of makes sense because,
    by
  • 5:04 - 5:08
    definition, Rails runs inside of other web
    servers, and
  • 5:08 - 5:11
    so, and that, this is really a concern kind
  • 5:11 - 5:12
    of at that web server layer.
  • 5:12 - 5:15
    So, Heroku, for example, has an experimental
    solution. Heroku
  • 5:15 - 5:20
    Labs is, Heroku's kind of unsupported experimental
    area features.
  • 5:20 - 5:24
    And you can run heroku labs:enable preboot,
    which will
  • 5:24 - 5:26
    start up new servers or dynos with your new
  • 5:26 - 5:29
    code, wait three minutes to give your Rails
    app
  • 5:29 - 5:31
    plenty of time to boot, and then switch traffic
  • 5:31 - 5:33
    over.
  • 5:33 - 5:37
    For, if you're using Puma or Unicorn, there
    are
  • 5:37 - 5:39
    facilities to start one worker at a time,
    or
  • 5:39 - 5:42
    groups of workers by sending signals to the
    master
  • 5:42 - 5:45
    process. HAProxy is a tool that I've used
    in
  • 5:45 - 5:48
    the past to kind of split traffic, give yourself
  • 5:48 - 5:52
    time to boot up another, another set of servers.
  • 5:52 - 5:54
    HAProxy is nice because you can do health
    checks
  • 5:54 - 5:57
    against those new servers and say, am I ready
  • 5:57 - 6:02
    to deploy? And Passenger also has some solutions
    around
  • 6:02 - 6:05
    this.
  • 6:05 - 6:06
    In terms of kind of the full scope of
  • 6:06 - 6:09
    zero downtime stuff, it gets more complicated
    when you
  • 6:09 - 6:13
    talk about database migrations and what's
    safe and what's
  • 6:13 - 6:16
    not safe for these, these types of, these
    types
  • 6:16 - 6:19
    of deployments. And I'm happy to chat about
    that
  • 6:19 - 6:21
    with anybody later, but that's out of scope
    for,
  • 6:21 - 6:23
    for this particular talk.
  • 6:23 - 6:24
    What I do want to drill into a little
  • 6:24 - 6:28
    bit is issues with static assets and zero
    downtime
  • 6:28 - 6:30
    deploys, because that's the thing that was,
    you know,
  • 6:30 - 6:32
    kind of at the heart of what I was
  • 6:32 - 6:35
    doing, was really dealing with these static
    Javascript assets.
  • 6:35 - 6:38
    And I think that these issues aren't discussed
    often.
  • 6:38 - 6:39
    So I kind of want to drill down, kind
  • 6:39 - 6:41
    of at a detailed level, and talk about them
  • 6:41 - 6:43
    here.
  • 6:43 - 6:46
    So when a browser makes an initial request
    to
  • 6:46 - 6:49
    your server for, to load your, your rich Javascript
  • 6:49 - 6:52
    app, it's loading the index dot html file
    and
  • 6:52 - 6:55
    by index.html, I'm gonna refer to the html
    file
  • 6:55 - 6:58
    that's the bootstrapping, it's bootstrapping
    your Javascript app. It's
  • 6:58 - 7:00
    the thing that has a little bit of code
  • 7:00 - 7:02
    to fire things up and it pulls in the
  • 7:02 - 7:04
    right Javascript and CSS assets.
  • 7:04 - 7:06
    So the request comes into your servers, your
    server
  • 7:06 - 7:10
    responds with the HTML file, with the text
    slash
  • 7:10 - 7:14
    html mine type, and typically the, your asset
    files,
  • 7:14 - 7:16
    the Javascript and CSS that are gonna be referenced
  • 7:16 - 7:19
    here, are gonna be fingerprinted. Right, so
    we do,
  • 7:19 - 7:22
    take a hash of the contents of the Javascript
  • 7:22 - 7:25
    file, we set it as a, we include that
  • 7:25 - 7:27
    hash in the file name, and we're then are
  • 7:27 - 7:30
    able to set far features expires headers on
    those
  • 7:30 - 7:33
    files, so that when the file changes, we don't
  • 7:33 - 7:36
    have to worry about cache expire or anything.
    We
  • 7:36 - 7:39
    just gotta new file that's gonna come through
    as
  • 7:39 - 7:41
    if the browser's never seen it before.
  • 7:41 - 7:44
    And so in this case, our html file might
  • 7:44 - 7:48
    contain something like assets slash app dash
    abc123 dot
  • 7:48 - 7:52
    js, where abc123 is this fingerprint we're
    talking about.
  • 7:52 - 7:55
    And so the browser takes that html, parses
    the
  • 7:55 - 7:57
    page, a short time later makes the request
    for
  • 7:57 - 8:02
    app dash abc123. Server says, here you go,
    some
  • 8:02 - 8:05
    text Javascript. Browser parses that, boots
    up the app.
  • 8:05 - 8:06
    All is well.
  • 8:06 - 8:09
    Hopefully this is very clear to everybody
    who's in
  • 8:09 - 8:12
    the room. What's maybe less clear, unless
    you've thought
  • 8:12 - 8:15
    about it in detail is that during deployments,
    this
  • 8:15 - 8:17
    idea can break down a little bit. And so
  • 8:17 - 8:20
    imagine that we've got our deployment and
    we've got
  • 8:20 - 8:23
    two kind of sets of server. The top set
  • 8:23 - 8:25
    that we're looking at here on the screen is
  • 8:25 - 8:28
    the existing code. The bottom set is the new
  • 8:28 - 8:30
    code that you're deploying.
  • 8:30 - 8:31
    And in this case, there's a change to the
  • 8:31 - 8:35
    Javascript file, so there's the new fingerprinted
    filename there.
  • 8:35 - 8:38
    So when our initial request for our page comes
  • 8:38 - 8:41
    in, it was, it will go to the old
  • 8:41 - 8:43
    code, because we haven't yet switched traffic
    over to
  • 8:43 - 8:47
    the, to the new deploy. And just like in
  • 8:47 - 8:49
    our first example, it's gonna come back with
    the
  • 8:49 - 8:54
    index file references app dash abc123 dot
    js.
  • 8:54 - 8:56
    Now, what if, in that moment, where, as the
  • 8:56 - 9:00
    page is being parsed, before this request
    comes back,
  • 9:00 - 9:02
    we then switch traffic over to pointing to
    the
  • 9:02 - 9:06
    new server? Well, request is gonna come in
    for
  • 9:06 - 9:09
    app dash abc123 dot js, the new server gets
  • 9:09 - 9:12
    the request and says, ah, I don't know what
  • 9:12 - 9:14
    you're talking about. I don't have a Javascript
    file
  • 9:14 - 9:18
    with that name. And so it says, 404 Not
  • 9:18 - 9:19
    Found.
  • 9:19 - 9:24
    And this is a challenging problem to, to address,
  • 9:24 - 9:26
    because there's a, because of a few reasons.
    One
  • 9:26 - 9:28
    is that most simply if I, at this point,
  • 9:28 - 9:31
    now hit refresh in the browser, of course
    everything
  • 9:31 - 9:33
    works fine. Right, because now both of those
    requests
  • 9:33 - 9:35
    are going to the new server and the world
  • 9:35 - 9:36
    is good.
  • 9:36 - 9:40
    It can be further kind of shadowed, this issue,
  • 9:40 - 9:43
    because, if you are serving your assets up
    through
  • 9:43 - 9:46
    an assets host at CBN, you might have some,
  • 9:46 - 9:49
    some, some of their, your edge nodes might
    have
  • 9:49 - 9:52
    that old page cached. That old Javascript
    cached. In
  • 9:52 - 9:54
    which case, those nodes will return it just
    fine.
  • 9:54 - 9:58
    And so, to, to know that you're, you know,
  • 9:58 - 10:01
    totally impervious to this, you know, might
    be a
  • 10:01 - 10:03
    little bit fuzzy, and also to be able to
  • 10:03 - 10:05
    reproduce it reliably is a challenging thing
    to do.
  • 10:05 - 10:08
    But, in short, to avoid these hiccups, both
    the
  • 10:08 - 10:11
    old versions of your assets and the new versions
  • 10:11 - 10:12
    have to be available for at least a few
  • 10:12 - 10:16
    minutes during your deployment in order to,
    to make
  • 10:16 - 10:19
    this zero downtime approach really work well
    on the
  • 10:19 - 10:22
    static asset front.
  • 10:22 - 10:23
    And so this was one of the things I
  • 10:23 - 10:26
    was thinking about during these many five
    minute deploys,
  • 10:26 - 10:30
    where I was, you know, wishing that I had
  • 10:30 - 10:32
    a solution. And so, in thinking about that,
    I
  • 10:32 - 10:34
    said, well, we could figure out how to do
  • 10:34 - 10:36
    this on our app servers. Kind of keep the
  • 10:36 - 10:39
    old versions of the, of the Javascript and
    the
  • 10:39 - 10:41
    new versions together. Or we could move the
    assets
  • 10:41 - 10:43
    elsewhere. And the idea of moving the assets
    elsewhere
  • 10:43 - 10:46
    really appealed to me, because that meant,
    if they
  • 10:46 - 10:48
    weren't on the Rails, my Rails servers, then
    maybe
  • 10:48 - 10:50
    I could avoid doing Rails deploys when I just
  • 10:50 - 10:53
    had static asset changes.
  • 10:53 - 10:55
    So I started to sketch out an idea. We've
  • 10:55 - 10:57
    got our Rails server at the top, and we
  • 10:57 - 10:59
    had this separate static asset server at the
    bottom.
  • 10:59 - 11:02
    Let's deploy our Rails app code to the Rails
  • 11:02 - 11:06
    servers, our Javascript, CSS, and images to
    these static
  • 11:06 - 11:09
    assets servers, and then we would deploy our
    index
  • 11:09 - 11:13
    file. Where would we deploy our index file?
  • 11:13 - 11:15
    And so, I started to think about, well, what
  • 11:15 - 11:16
    is the index file? It is kind of this
  • 11:16 - 11:18
    thing that bridges the two? What are the requirements
  • 11:18 - 11:20
    around it? What do we know about it? And
  • 11:20 - 11:24
    this index, the html file points to fingerprinted
    Javascript
  • 11:24 - 11:27
    and CSS, but it's not fingerprinted itself.
    That's obviously
  • 11:27 - 11:31
    important, because it needs to be at, at a
  • 11:31 - 11:35
    consistent location for browsers to locate,
    to load. It
  • 11:35 - 11:38
    contains Javascript urls and code to boot
    the Javascript
  • 11:38 - 11:40
    app to load CSS and such in the right
  • 11:40 - 11:44
    order. It's a good place to provide environment-specific
    configuration
  • 11:44 - 11:47
    to Javascript. Maybe you have some differences
    between dev
  • 11:47 - 11:50
    and stage in production.
  • 11:50 - 11:51
    One thing that I knew was key, because I
  • 11:51 - 11:53
    had struggled with it, is that when you serve
  • 11:53 - 11:55
    this html off of the same domain as your
  • 11:55 - 11:59
    API, life is way simpler with respect to cores
  • 11:59 - 12:04
    and cross origin security issues. And finally,
    if I
  • 12:04 - 12:06
    wanted, if you wanted to be able to deploy
  • 12:06 - 12:09
    changes quickly to your users, caching on
    this particular
  • 12:09 - 12:12
    page should be minimal to none, so that you
  • 12:12 - 12:15
    can pretty instantly switch over.
  • 12:15 - 12:18
    So my conclusion, from thinking about this,
    is that
  • 12:18 - 12:20
    the html page ideally should be managed and
    thought
  • 12:20 - 12:23
    about as part of your static asset deployment
    process,
  • 12:23 - 12:25
    but it should be served off of your Rails
  • 12:25 - 12:26
    server.
  • 12:26 - 12:29
    And, as importantly, it should be served off
    your
  • 12:29 - 12:33
    Rails server without requiring a Rails reboot
    or redeploying
  • 12:33 - 12:37
    the entire Rails app. And so, so we were
  • 12:37 - 12:39
    able to start to refine this sketch and say,
  • 12:39 - 12:42
    OK, our Rails server's gonna be serving up
    our
  • 12:42 - 12:45
    API requests, our, kind of, traditional, dynamic
    Rails pages
  • 12:45 - 12:47
    as part of the html for a Javascript app,
  • 12:47 - 12:49
    and our static asset server's gonna be serving
    up
  • 12:49 - 12:52
    the Javascript, CSS and images.
  • 12:52 - 12:54
    And so that means we need to somehow deploy
  • 12:54 - 12:59
    our html up to the Rails server without a
  • 12:59 - 13:00
    full, a full reboot. And so, how could we
  • 13:00 - 13:01
    do this?
  • 13:01 - 13:03
    Well, the most obvious thing to me was, well,
  • 13:03 - 13:06
    take a html file and put it on the
  • 13:06 - 13:10
    file system of each Rails server. And this
    has
  • 13:10 - 13:12
    a few things that aren't great about it. You
  • 13:12 - 13:15
    can probably make this work in some configurations.
    In
  • 13:15 - 13:19
    many deployment environments, disk is ephemeral,
    and so relying
  • 13:19 - 13:22
    on, you know, on copying some things up might
  • 13:22 - 13:24
    not be a great idea. It's also a little
  • 13:24 - 13:27
    bit weird to mix assets, files deployed from
    a
  • 13:27 - 13:31
    particular gitshaw, with files deployed from
    somewhere else, kind
  • 13:31 - 13:34
    of in the same file system.
  • 13:34 - 13:35
    And so, we said, well, what if there's something
  • 13:35 - 13:38
    that we could all see and talk to? Well,
  • 13:38 - 13:41
    what about uploading to S3? So then all the
  • 13:41 - 13:45
    Rails servers can, can see S3, read from it,
  • 13:45 - 13:47
    be able to serve up that html. And this
  • 13:47 - 13:49
    could kind of work, but reading from S3 is
  • 13:49 - 13:52
    a little bit slow. And we wanted this page
  • 13:52 - 13:56
    to be fast, obviously. No Javascript or CSS
    is
  • 13:56 - 13:58
    gonna start being loaded until this page is
    loaded
  • 13:58 - 14:00
    in the browser. And so the fastest we could,
  • 14:00 - 14:01
    the faster we could get this page to the
  • 14:01 - 14:03
    user, the better.
  • 14:03 - 14:06
    And so, then we said, well, what about redis?
  • 14:06 - 14:10
    Redis is persistent. It's fast. For us, it
    was
  • 14:10 - 14:13
    already in our environment. We liked this
    idea a
  • 14:13 - 14:16
    lot. We decided to, to dig in. This is
  • 14:16 - 14:18
    not the, as you'll see, this is not the
  • 14:18 - 14:21
    only way to do it. It's totally possible to
  • 14:21 - 14:24
    user other systems besides redis. But redis
    kind of
  • 14:24 - 14:27
    firt the bill for us and works quite well,
  • 14:27 - 14:28
    as you'll see.
  • 14:28 - 14:30
    So, the general idea was we're gonna deploy
    into
  • 14:30 - 14:32
    redis and then serve out of redis via a
  • 14:32 - 14:36
    Rails controller. So here's the simplest possible
    kind of
  • 14:36 - 14:39
    deploy code that we had. It's a rig task,
  • 14:39 - 14:43
    and we're going to generate our html for the
  • 14:43 - 14:46
    current assets, and that's, that's kind of
    an exercise
  • 14:46 - 14:48
    for your build tooling, which we'll talk about
    a
  • 14:48 - 14:50
    little bit later. And then once we had this
  • 14:50 - 14:52
    html, we're actually going to set it as a,
  • 14:52 - 14:56
    at a, as a redis key-value store. So the
  • 14:56 - 14:58
    html is the value and the key would be
  • 14:58 - 15:03
    something well-known like jsapp colon index,
    for example. And,
  • 15:03 - 15:05
    and this is a redis connection that's connecting
    directly
  • 15:05 - 15:08
    to your charted deployment environment. So
    that's staging more
  • 15:08 - 15:10
    production.
  • 15:10 - 15:13
    Once it's there in redis, our controller,
    again, the
  • 15:13 - 15:16
    most simplest version, is get the value out
    of
  • 15:16 - 15:21
    redis. Render text.html. Now, when I first
    looked at,
  • 15:21 - 15:23
    looked at this code or wrote this code, I
  • 15:23 - 15:24
    said, is that gonna be served up with the
  • 15:24 - 15:27
    right mine type? Seems a little strange. And
    it
  • 15:27 - 15:30
    turns out that, yes, if you do render text
  • 15:30 - 15:33
    and some string, Rails serves that up with
    text
  • 15:33 - 15:37
    slash html, if you don't specify. So, it's
    a
  • 15:37 - 15:39
    little, I think a little bit of a confusing
  • 15:39 - 15:41
    API, but it does what we want.
  • 15:41 - 15:44
    So, we can now continue to refine this approach.
  • 15:44 - 15:46
    We know we're deploying, when we need to deploy
  • 15:46 - 15:49
    Rails app code, we're doing a deployment to
    our
  • 15:49 - 15:52
    Rails server. When we're deploying Javascript,
    CSS and images,
  • 15:52 - 15:55
    we're deploying to the static assets server,
    and then
  • 15:55 - 15:58
    we're deploying html by connecting to redis
    and deploying
  • 15:58 - 15:59
    into it.
  • 15:59 - 16:03
    And we can make things a little bit nicer
  • 16:03 - 16:07
    by dropping cloud front right in front of,
    by
  • 16:07 - 16:09
    using S3 as our static assets server, and
    then
  • 16:09 - 16:11
    dropping cloud front instead of in front of
    our,
  • 16:11 - 16:13
    in front of S3. So, for very little effort
  • 16:13 - 16:16
    and very little money, we've got now CBN distribution
  • 16:16 - 16:18
    for our static assets.
  • 16:18 - 16:22
    Now, there's a few things about this deployment
    to
  • 16:22 - 16:25
    S3, in terms of making it fast. Getting a
  • 16:25 - 16:29
    file list from S3 can be somewhat expensive,
    particularly
  • 16:29 - 16:32
    the more files that you have. And so the
  • 16:32 - 16:34
    approach that we took was to generate a manifest
  • 16:34 - 16:39
    file of our current assets and store the copy
  • 16:39 - 16:42
    of that manifest on S3 so we, we're basically
  • 16:42 - 16:44
    gonna read the remote manifest, compare it
    to our
  • 16:44 - 16:46
    local manifest, and know we only need to deploy
  • 16:46 - 16:49
    what's different. And so this means that if
    I
  • 16:49 - 16:52
    make one Javascript, one line of Javascript
    change, it's
  • 16:52 - 16:55
    just the file that that's concatenated into
    that needs
  • 16:55 - 16:57
    to be updated, and not all of my images
  • 16:57 - 17:00
    and CSS, as we're doing our deploy, our assets
  • 17:00 - 17:02
    deploy to S3.
  • 17:02 - 17:05
    Now, purging has been on our to-do list for
  • 17:05 - 17:08
    this architecture for quite a while, right.
    After a
  • 17:08 - 17:12
    deploy is successfully completed, we can,
    in theory, remove
  • 17:12 - 17:15
    stuff from S3. We never really got around
    to
  • 17:15 - 17:18
    that. Mostly because it's so incredibly cheap
    to store
  • 17:18 - 17:21
    these small files on S3, so, still on our
  • 17:21 - 17:24
    to-do list. I would probably not recommend
    you prioritizing
  • 17:24 - 17:27
    it too high for yourself. The code for this
  • 17:27 - 17:30
    S3 sink is here at this link. I will
  • 17:30 - 17:31
    make these slides available. You don't have
    to worry
  • 17:31 - 17:34
    about copying it down. This repo is open source
  • 17:34 - 17:36
    and contains a lot of the code that we're
  • 17:36 - 17:38
    looking at today. And it's the actual code
    that
  • 17:38 - 17:41
    we use for our, for our production environments.
  • 17:41 - 17:45
    So, once you start thinking about this architecture,
    it
  • 17:45 - 17:48
    paves the way to do something a little bit
  • 17:48 - 17:50
    more fundamental with your rich Javascript
    app and your
  • 17:50 - 17:53
    Rails app, which is that you pull them, tease
  • 17:53 - 17:56
    them apart into separate repositories. And
    now why would
  • 17:56 - 17:57
    you want to do that?
  • 17:57 - 17:59
    Well, one of the reasons is, you know, thinking
  • 17:59 - 18:02
    about tagging, do, you know, kind of tagging
    a
  • 18:02 - 18:05
    deployed version of your code. Since you've
    got these
  • 18:05 - 18:08
    independent deploy processes, it makes sense
    to be able
  • 18:08 - 18:11
    to tag a Javascript deploy separate from a
    Rails
  • 18:11 - 18:14
    deploy, because they really are now independent
    of each
  • 18:14 - 18:15
    other.
  • 18:15 - 18:18
    And I find also that thinking of your Javascript
  • 18:18 - 18:22
    app as a separate, independent client of your
    API,
  • 18:22 - 18:23
    works really well. Kind of puts it on the
  • 18:23 - 18:25
    same level as a native app, for example, maybe
  • 18:25 - 18:27
    if you've got an iPhone app that communicates
    to
  • 18:27 - 18:29
    your API as well.
  • 18:29 - 18:33
    It also opens up the realm of possibility
    to
  • 18:33 - 18:35
    having a lot of flexibility with what kind
    of
  • 18:35 - 18:37
    build tools you want to use with your Javascript
  • 18:37 - 18:41
    app. You may choose to use sprockets in your
  • 18:41 - 18:44
    separate standalone repo. Or you may choose
    to use
  • 18:44 - 18:49
    grunt, gulp, broccoli, brunch. You name it,
    there's obviously
  • 18:49 - 18:53
    a lot of innovation and creativity happening
    around build
  • 18:53 - 18:56
    tools in the Javascript environment. And my
    guess would
  • 18:56 - 18:59
    be that you're, we're gonna see faster, you
    know,
  • 18:59 - 19:04
    innovation, iteration in the Javascript environment
    for building Javascript
  • 19:04 - 19:06
    apps than we will in the Ruby environment
    for
  • 19:06 - 19:07
    building Javascript apps.
  • 19:07 - 19:12
    So, we've now got an approach that works pretty
  • 19:12 - 19:15
    well. But I think the, the question is, is
  • 19:15 - 19:17
    it worth doing the work to set this in
  • 19:17 - 19:19
    place? Like, how fast is this in the real
  • 19:19 - 19:21
    world? And so I took one of our apps,
  • 19:21 - 19:23
    and this isn't, was not a scientific bench
    mark,
  • 19:23 - 19:27
    so consider it directional. And our builds
    took about
  • 19:27 - 19:30
    six and a half seconds. This particular app
    is
  • 19:30 - 19:32
    using Rake Pipeline as a build tool for the
  • 19:32 - 19:38
    Javascript side. Our transferring assets to
    S3 using this
  • 19:38 - 19:40
    differential approach was about a second,
    and then uploading
  • 19:40 - 19:44
    html into redis was about two and a half
  • 19:44 - 19:48
    seconds. And so, instead of a five minute
    deploy,
  • 19:48 - 19:50
    I was now able, we were now able to
  • 19:50 - 19:54
    deploy this, our Javascript apps in under
    ten seconds.
  • 19:54 - 19:58
    So just by that, that was a big win.
  • 19:58 - 20:00
    And stopped me from wanting to throw things
    across
  • 20:00 - 20:03
    the office. But, I think that, you know, in
  • 20:03 - 20:07
    any kind of architectural choices like this,
    you learn
  • 20:07 - 20:09
    if this is a good idea or not over
  • 20:09 - 20:12
    time, right, based on, how does, how does
    this
  • 20:12 - 20:15
    architecture respond to changes. What kind
    of possibilities does
  • 20:15 - 20:17
    it enable? So I want to talk a little
  • 20:17 - 20:20
    bit about the kind of emergent behavior that
    we've
  • 20:20 - 20:22
    seen around, now that we've had this in production
  • 20:22 - 20:24
    for awhile.
  • 20:24 - 20:26
    The first thing is, the idea of preview. And
  • 20:26 - 20:29
    so this is an actual command line session
    for
  • 20:29 - 20:32
    deploying an app. In this case, it's yapp-prefs,
    which
  • 20:32 - 20:36
    is our, kind of, account settings app. We
    first
  • 20:36 - 20:41
    run rake dist. This is the build. And with
  • 20:41 - 20:43
    the build completes, in this case, as we saw
  • 20:43 - 20:44
    in the pie chart before, in about six seconds
  • 20:44 - 20:48
    or so. And it says, OK, to deploy these
  • 20:48 - 20:53
    assets to S3, run rake deploy:assets with
    this, what
  • 20:53 - 20:55
    we call a manifest idea, this b35b.
  • 20:55 - 20:58
    So what's a manifest id? We talked earlier
    about
  • 20:58 - 21:01
    fingerprinting assets and we talked about
    creating a manifest
  • 21:01 - 21:03
    file. So what we do also is we fingerprint
  • 21:03 - 21:05
    the manifest file. So we take a hash of
  • 21:05 - 21:08
    the contents of that manifest file and we
    say,
  • 21:08 - 21:10
    OK, that is the manifest id for this deploy.
  • 21:10 - 21:12
    And that's, it's, it's kind of a unique identifier
  • 21:12 - 21:15
    as it's going through its unique deploy process.
  • 21:15 - 21:17
    And so we run rake deploy:assets, which does
    the
  • 21:17 - 21:21
    differential upload to S3 and it's gonna show
    us
  • 21:21 - 21:23
    what it uploads. It's gonna spit out, OK,
    I've
  • 21:23 - 21:29
    uploaded these four files. JS, CSS, two yaml
    files
  • 21:29 - 21:31
    for the manifest. We, actually, these are
    two copies
  • 21:31 - 21:33
    of the same thing. One is a, has a
  • 21:33 - 21:35
    file name with the id, and one just is,
  • 21:35 - 21:38
    hey I am the latest. And it's going to
  • 21:38 - 21:41
    then tell us the next command to run is
  • 21:41 - 21:46
    deploy:generate_index for this manifest id.
    And what this is
  • 21:46 - 21:49
    gonna do is going to connect to redis and
  • 21:49 - 21:52
    set this at a key named for the manifest
  • 21:52 - 21:54
    id. So in the previous simplistic example
    we looked
  • 21:54 - 21:57
    at, it was just updating jsapp index. Now
    it's
  • 21:57 - 22:00
    updating a key at prefs, in this case, prefs:index
  • 22:00 - 22:04
    b35b something, you know, named for the manifest
    id.
  • 22:04 - 22:07
    And why is this awesome? Well, this command
    line
  • 22:07 - 22:10
    tool can now tell us, hey, to preview this,
  • 22:10 - 22:13
    this asset change, go ahead and take a look
  • 22:13 - 22:17
    at your at, your site with the query param
  • 22:17 - 22:22
    manifest id equals b35 et cetera. And what
    this
  • 22:22 - 22:24
    is gonna do is it's going to pull the
  • 22:24 - 22:27
    new html file from redis. Gonna show, which
    is
  • 22:27 - 22:30
    loading up the new Javascript. It's connecting
    to the
  • 22:30 - 22:33
    production API. So you're able to smoke test
    this
  • 22:33 - 22:36
    in production. Everything is working just
    as the user
  • 22:36 - 22:39
    will see it, except for your users don't see
  • 22:39 - 22:41
    it yet. So if you screwed something up, you've
  • 22:41 - 22:43
    got a chance before kind of pulling the trigger
  • 22:43 - 22:45
    and switching it life to go.
  • 22:45 - 22:48
    And then, finally, one more command to kind
    of
  • 22:48 - 22:50
    active that redis key and make it the current
  • 22:50 - 22:54
    key. And so what does this code look like?
  • 22:54 - 22:56
    It's actually not that much more complicated
    than what
  • 22:56 - 22:59
    we saw before. We invoke our rake task with
  • 22:59 - 23:05
    the manifest id. Generate our html file. And
    then,
  • 23:05 - 23:08
    instead of setting jsapp index, we'll set
    a jsapp
  • 23:08 - 23:10
    key based on the manifest name, or redis key
  • 23:10 - 23:13
    based on the manifest name. And then spit
    out
  • 23:13 - 23:16
    something to give the developer the url to
    take
  • 23:16 - 23:20
    a look at, to preview, to preview the app.
  • 23:20 - 23:23
    On the server-side, we're gonna add one more
    redis
  • 23:23 - 23:28
    request to the mix. If there's a manifest
    id
  • 23:28 - 23:31
    param, then we'll just use that. If it's blank,
  • 23:31 - 23:33
    then we'll go and we'll connect to a current
  • 23:33 - 23:36
    key. Grab the manifest id, then use that to
  • 23:36 - 23:40
    serve up the current version of, of the site.
  • 23:40 - 23:42
    Of the, of this index file.
  • 23:42 - 23:46
    And so, that has been pretty powerful, and
    it's,
  • 23:46 - 23:48
    it's a super useful tool that we use in
  • 23:48 - 23:51
    almost every single deploy. The developer's
    just gonna do
  • 23:51 - 23:54
    a quick smoke test and say, yes, everything
    looks
  • 23:54 - 23:56
    good, before they flip the switch.
  • 23:56 - 24:01
    The next interesting aspect that this kind
    of enabled
  • 24:01 - 24:05
    is around dynamic html rewriting. And so what
    we
  • 24:05 - 24:09
    realize is that as html was passing through
    from
  • 24:09 - 24:12
    redis through the controller back to the browser,
    we
  • 24:12 - 24:15
    had the opportunity to make adjustments if
    we wanted
  • 24:15 - 24:19
    to. And one category of adjustment that we
    ended
  • 24:19 - 24:22
    up making, as you see in this example, is
  • 24:22 - 24:25
    injecting some information about the current
    user.
  • 24:25 - 24:28
    Now, obviously in a Rails controller we know
    typically
  • 24:28 - 24:31
    who the current user is. Most apps will have
  • 24:31 - 24:35
    a current_user method available to any controller
    that they
  • 24:35 - 24:39
    can, it can grab. In contrast, when you're
    booting
  • 24:39 - 24:42
    up a Javascript MVC app, at that point, you
  • 24:42 - 24:44
    know, most apps don't know who the current
    user
  • 24:44 - 24:46
    is. And if, in most, you know mostly there
  • 24:46 - 24:49
    is some XHR request that's involved in figuring
    out,
  • 24:49 - 24:51
    is this user logged in and, if so, what
  • 24:51 - 24:54
    is their role in the system? And during that
  • 24:54 - 24:57
    time, the user is sitting there waiting, right.
    The
  • 24:57 - 24:59
    Javascript is just kind of, maybe it's rendering
    a
  • 24:59 - 25:02
    loading spinner for the user or something.
    But it's
  • 25:02 - 25:04
    a little bit annoying and it also makes the
  • 25:04 - 25:08
    boot process for the, for your Javascript
    app more
  • 25:08 - 25:08
    complicated.
  • 25:08 - 25:11
    So we said, well what if the app could
  • 25:11 - 25:15
    have, at boot time, have access to this information?
  • 25:15 - 25:18
    So what we're doing here is, in the controller
  • 25:18 - 25:21
    action at the top, between the time we get
  • 25:21 - 25:25
    the html out of redis and return, render it,
  • 25:25 - 25:28
    we're going to inject current_user information.
    We're gonna grab
  • 25:28 - 25:32
    the current_user and then user our AcitveModel
    serializer to
  • 25:32 - 25:35
    convert it to JSON, escape it, and stick it
  • 25:35 - 25:37
    into the head tag.
  • 25:37 - 25:40
    And you'll notice that the method that we're
    using
  • 25:40 - 25:43
    to add this to the html, you might find
  • 25:43 - 25:46
    a little crude. And certainly when I first
    started
  • 25:46 - 25:49
    doing this, I said, OK, we'll use Nokogiri.
    We'll
  • 25:49 - 25:52
    parse the html. And then we'll insert, you
    know,
  • 25:52 - 25:55
    insert a node, and then we'll render, you
    know,
  • 25:55 - 25:58
    convert that back to text. And it turns out
  • 25:58 - 26:01
    that really what we're doing is so simple
    that,
  • 26:01 - 26:03
    as fast as Nokogiri is, and it's pretty fast
  • 26:03 - 26:06
    for an XML or html parser, it is not
  • 26:06 - 26:09
    faster than string manipulation and string
    indexing.
  • 26:09 - 26:11
    And so, in this case, we're just looking at,
  • 26:11 - 26:12
    where's the end of the head tag or the
  • 26:12 - 26:15
    beginning of the head tag, inject this meta
    tag
  • 26:15 - 26:18
    in there. And this works great.
  • 26:18 - 26:21
    The, some other use cases for this same approach
  • 26:21 - 26:25
    might be injecting csrf tokens if you, you
    know,
  • 26:25 - 26:28
    need to interact with Rails forms from, from
    your
  • 26:28 - 26:32
    Javascript app. Including dynamic analytics
    params, we had a
  • 26:32 - 26:36
    case where the, the Javascript app was kind
    of
  • 26:36 - 26:39
    the, the, a goal page for a Google analytics
  • 26:39 - 26:42
    kind of flow. And we needed to set some,
  • 26:42 - 26:45
    certain Javascript only in some conditions.
    So this was
  • 26:45 - 26:46
    a nice way to do that.
  • 26:46 - 26:49
    If you're using feature flags through something
    like the
  • 26:49 - 26:54
    excellent rollout gem, it's great that that's
    available throughout
  • 26:54 - 26:55
    Rails. But how do you make it available inside
  • 26:55 - 26:58
    of your Javascript app also, this is a nice
  • 26:58 - 27:00
    solution to be able to kind of inject variables
  • 27:00 - 27:04
    along those lines.
  • 27:04 - 27:07
    Another pretty awesome thing that we've been
    able to
  • 27:07 - 27:10
    do using this approach is doing A/B testing
    within
  • 27:10 - 27:14
    our Javascript app. And we've experimented
    with two different
  • 27:14 - 27:17
    kinds. One is, kind of, setting some flags
    based
  • 27:17 - 27:20
    on which bucket the user ends up in, A
  • 27:20 - 27:23
    or B. This is pretty similar to what we
  • 27:23 - 27:27
    just described. And then the, the second is
    serving
  • 27:27 - 27:30
    up wholly different html based on the A/B
    bucket.
  • 27:30 - 27:33
    We'll, I'll talk, take you through each of
    those.
  • 27:33 - 27:37
    So this is using the split gem, which, if
  • 27:37 - 27:39
    you haven't seen, is a great A/B tool, A/B
  • 27:39 - 27:42
    test tool for Ruby frameworks, web frameworks.
    It has
  • 27:42 - 27:46
    a Rails integration that gives us the A/B
    test
  • 27:46 - 27:49
    method, which you see on line eleven there.
    And
  • 27:49 - 27:52
    so we're doing some injection into our html
    where
  • 27:52 - 27:55
    we're saying, if the user is part of the
  • 27:55 - 27:59
    show walkthrough experiment, then we're going
    to inject the
  • 27:59 - 28:02
    script tag that just sets a global variable.
    And
  • 28:02 - 28:04
    then our Javascript app as it boots in is
  • 28:04 - 28:06
    running, is, is, can consult that global variable
    to
  • 28:06 - 28:09
    decide whether to show the walkthrough that
    you're doing,
  • 28:09 - 28:10
    doing some testing on.
  • 28:10 - 28:14
    And then later, elsewhere in your app, you
    would
  • 28:14 - 28:17
    indicate that, that the goal was achieved,
    that the
  • 28:17 - 28:20
    user signed up or published or whatever it
    is
  • 28:20 - 28:23
    that was kind of the, the goal for you
  • 28:23 - 28:26
    A/B test. And so this was great. This is
  • 28:26 - 28:28
    excellent for the kinds of A/B tests where
    both
  • 28:28 - 28:33
    paths are supported by your, by a given incarnation
  • 28:33 - 28:35
    of your Javascript app.
  • 28:35 - 28:39
    We had another case, though, where, this was
    right
  • 28:39 - 28:41
    around the time that iOS7 released and there
    was,
  • 28:41 - 28:43
    if you recall, there was a lot of excitement
  • 28:43 - 28:46
    in, around flat design. And so we did a
  • 28:46 - 28:49
    redesign of our Javascript app. We weren't
    immune to
  • 28:49 - 28:55
    the hype. And, but as we got near completion,
  • 28:55 - 28:57
    we started to wonder, well, we're gonna release
    this.
  • 28:57 - 28:58
    How do we know that this is any better
  • 28:58 - 29:01
    than our existing site? Is this going to improve
  • 29:01 - 29:04
    our metrics or hurt them? And we wanted to
  • 29:04 - 29:06
    try to get some confidence one way or the
  • 29:06 - 29:07
    other.
  • 29:07 - 29:09
    So we said, well, we've been developing this
    in
  • 29:09 - 29:11
    a branch, and we already know that we can
  • 29:11 - 29:14
    preview different versions of our Javascript
    app, because we
  • 29:14 - 29:16
    had this preview mechanism in place. Could
    we use
  • 29:16 - 29:18
    this for A/B testing, also?
  • 29:18 - 29:21
    And so, it turns out that we were able
  • 29:21 - 29:24
    to just update our deploy scripts to be able
  • 29:24 - 29:27
    to deploy from a branch into an experiment
    kind
  • 29:27 - 29:30
    of redis key, and then use the same A/B
  • 29:30 - 29:34
    test mechanism to determine, for the new design
    experiment,
  • 29:34 - 29:36
    should the user use the current manifest or
    the
  • 29:36 - 29:39
    experimental manifest?
  • 29:39 - 29:41
    Once we made that decision, we then got the
  • 29:41 - 29:45
    appropriate manifest id from redis, got the
    html, rendered
  • 29:45 - 29:48
    it, and users either saw our old app or
  • 29:48 - 29:52
    the new flat-design app. Turns out, flat design,
    about
  • 29:52 - 29:56
    nine percent better. So good news.
  • 29:56 - 29:58
    So this is suitable for changes where your
    development's
  • 29:58 - 30:00
    capping at a branch, and you want to A/B
  • 30:00 - 30:03
    between the branches. Not a common scenario,
    but if
  • 30:03 - 30:05
    it's, when it's, it's useful, it was great
    to
  • 30:05 - 30:10
    see how this approach supported that architecture.
  • 30:10 - 30:12
    One more possibility that I didn't cover here,
    but
  • 30:12 - 30:17
    you can imagine how this might work, is around
  • 30:17 - 30:20
    doing rollback. So what if every time that
    we
  • 30:20 - 30:24
    did this deploy and updated our html in redis
  • 30:24 - 30:26
    we pushed into a redis list and said, hey,
  • 30:26 - 30:29
    so and so user deployed such and such manifest
  • 30:29 - 30:32
    id at this time, and then we were able
  • 30:32 - 30:34
    to have a rake task that reads that list
  • 30:34 - 30:37
    and lets you rollback to any particular version.
    Hopefully,
  • 30:37 - 30:39
    at this point, you can see how straightforward
    that
  • 30:39 - 30:42
    would, that kind of thing would be as well.
  • 30:42 - 30:44
    So, with that, I want to say thank you
  • 30:44 - 30:47
    to my colleagues at Yapp Labs who helped create
  • 30:47 - 30:51
    this. Kris Seldon, Stefan Penner and Ray Cohen.
    And
  • 30:51 - 30:52
    while we were working on this, we had heard
  • 30:52 - 30:57
    some rumors about some Square engineers doing
    a, using
  • 30:57 - 30:59
    a similar approach at Square. So we took some
  • 30:59 - 31:02
    inspiration from those rumors as well, and
    so thank
  • 31:02 - 31:05
    you, nameless Square engineers, or if anybody's
    here. Love,
  • 31:05 - 31:08
    love to chat with you about it.
  • 31:08 - 31:12
    We've got some time for questions, and so
    I
  • 31:12 - 31:14
    want to open it up to all of you.
  • 31:14 - 31:16
    Question in the middle?
  • 31:16 - 31:17
    All right, cool. Thank you all so much. Appreciate
  • 31:17 - 31:19
    it. Enjoy the rest of the conference.
Title:
RailsConf 2014 - Lightning Fast Deployment of Your Rails-backed JavaScript app
Description:

more » « less
Duration:
31:44

English subtitles

Revisions