 Talk I'm going to give is Avented Ruby versus Node.js. First, a little bit about myself. My name is Jerry. I work at GitHub on the Enterprise team. I primarily work in Ruby and JavaScript, but in the last year, I've been playing with Avented programming a bit, and I kind of wanted to share some of the things I've come across and how you can use it in your own projects. Before I go into the talk, here's kind of the things I keep in the back of my head whenever I think about performance problems. I think whenever we optimize for performance, it should always be something that helps our end users. A lot of the times we go after these kind of micro-optimizations where it's very cool technically, but really it's not worth the level of effort that you have to put in. So it's important to kind of figure out the lowest-hanging fruit as far as performance problems goes in your project. And to that end, you really want to make sustainable changes in your projects, changes that are relatively straightforward to make, but easy to maintain in the long run. You don't want to come back six months later and be like, I have no idea why I did this. It's all broken now. Was it really worth it? So an overview of what I'm going to cover, a lot of evented stuff. I'll give a brief intro to what evented programming is at a very high level and then cover how that applies to server-side programming and specifically to the web. Then I'll go over some of the similarities and differences of doing evented programming in JavaScript versus doing it in Ruby and what some of the pros and cons are for each of those. After that, I'll talk about how you can apply evented programming techniques to your existing application, either within your Rails app or writing specific features to run alongside your web app. So evented program, what is it? All it is is registering function callbacks for events that we care about. So we already do this every day when we're doing client-side JavaScript. This snippet of code is just saying whenever there's a click on the DOM, we should change the text color to red. And that little code snippet is an example of the reactor pattern. So what the reactor pattern is, you have a system called the reactor that's responsible for listening to the events that are triggered. And in our case, the reactor is a browser, for example. When it notices there's a click event or a drag event, it delivers those events to any callbacks you register. Certain domains naturally lend themselves to this evented reactor pattern. When we're talking about things like UI events, keyboard, mouse, and touch, there's no way for us to kind of anticipate when those are going to happen. So obviously, you have to define the handlers for those and register them at the beginning of your program. So when these events do happen, you know what to do. And the nice thing about these reusable reactors is that it saves us a lot of time in plumbing. If we didn't have these, then suddenly we'd spend all of our time writing mouse detection instead of building useful things. But the thing to keep in mind is that even though it makes, it's very intuitive to have UI events, you can really define events for arbitrary things. So another event might be when the wireless becomes available or when there's data available on the disk for reading. From the Node.js site, we see that Node.js uses an event driven non-blocking IO model, and it kind of overlaps with some of the terms we're talking about, but what does it actually mean in a server side context? So unlike the browser example, you can think of Node as a general purpose reactor, so instead of dealing with mouse clicks and events, you can have it as a reactor to deliver arbitrary events that you care about. And as far as blocking IO goes, I love this quote, an image from Christian parties, he says, if RAM was an F18 Hornet with a max speed of 1200 miles an hour, then disk access speed is a banana slug with a top speed of 7,000 of a mile per hour. And intuitively, I think we all have a sense for this, that CPU is going to be faster than memory, memory is going to be faster than disk and disk faster than network access. But the thing to keep in mind is whenever there isn't data available for the CPU to process on, then we have to idle the CPU and wait for that. So the operating system and hardware caches basically hides this from us when we say file that read, file that text, it works like we would expect. But under the hood, the CPU might be idling and you're wasting processing basically. Luckily for us, the OS is smart enough to basically switch between different processes when it notices that one CPU isn't busy. So the reason why when we load up a song on iTunes, it doesn't cause the rest of our system to kind of grind into a halt. You can still do stuff in your browser and your Rails process will keep running. And it's really nice because even though this might not be the most optimal way, it's really simple, this process concurrency. What Node.js gives us is it optimizes the IO step one step further by pushing some of the responsibility into the application layer. So rather than having just the operating system switch between processes, now you have a reactor in the node process and whenever there's IO callbacks, it's able to switch between tasks internally. So up until now we were talking about evented programming, but why should we care about this for web apps, since we already have process concurrency? Well, the reason we should care is because we are still doing a lot of blocking IO all over the place and I really enjoyed Aaron's talk about how the direction we're going to go in the future for kind of slimming this down and taking advantage of more of our resources. So a few examples of things that we're going to run into every day is we're definitely going to touch a database. We're going to hit external APIs and that's going to be a very slow blocking coil. And even things like when you resize an image, when you show out to a different process, you're basically waiting for the contents of that process to finish before you can get back to processing. So if we were writing a little controller to save tweets, for example, in the first line we're creating a new tweet object, then we might want to shorten any links in the text and then we want to save the tweet to a database. So even in just these three lines, we're going out to the network and we're also touching the file system. And when we are blocking on IO, what happens is from the CPU's perspective is we can't handle more than one incoming request at a time. We have to finish the first request before we can start handling another incoming user. And the way we get around this right now today in Rails is we spool up multiple Rails processes and each of those handles one user at a time. It's simple, it's nice, it works, but it chews through a lot of memory. If we rewrote that same little bit of code in Node, what we do is change all of the blocking calls into function callbacks. So instead of calling shortened links and save directly, we basically say here is what you should do when you finish. So shorten my links when that's done, call this function callback, save my tweet to the database when that's done, call the function callback. And when we draw out what the concurrency looks like here, when we start Node, we're already running in a reactor, so that's the first line on the top. And when the first request comes in, we start doing some processing, but because all we're doing is registering callbacks, we finish really quickly and give control right back to the reactor. So that as soon as that first red line is happening, which is the blocking IO from the first request, the reactor thread is what has control, so it's able to start another request while the CPU isn't doing anything. At some point later, the IO finishes for the first request, so we invoke the callback and same with the last request. Notice that even though we're able to start new requests when blocking IO is going on, only one thing is going on at a time, we don't have to worry about race conditions and deadlocks. We're just switching between these tasks when the CPU isn't doing anything. So with Node, because the reactor can switch between requests internally, each reactor can process more requests. And the nice thing is if one of the Node processes becomes saturated, you can still get process concurrency by spooling up another Node process. The important thing to keep in mind is there's a distinction between latency and concurrency. So even though the Node process can handle more requests in that one process for the same amount of memory, if it was just one incoming request for Ruby and one incoming request for JavaScript, it's going to take the same amount of time for it to finish processing. So if you have a really slow request, it's better to optimize the response latency because that's what your users are actually going to notice. And the trade-off on the Node side of things is that suddenly when we look at this code, the application code is aware of blocking IO. So our domain model is Tweets, but somehow we're thinking about blocking IO because we need to. And coming from Ruby, that's especially hard to swallow because we're so used to really maintainable, really readable code. But now we have to kind of deal with this callback spaghetti. So now we know that Node can give us better concurrency in the back-end rails, but the caveat is that kind of ugly callback soup we saw in the last slide. So the question we want to ask is, can we get some of the same benefits with Ruby but without the drawbacks? So Ruby is definitely capable of evented programming, and it's also a general purpose language. So typically when we're writing Ruby, for the most part we're writing it procedurally, so each line executes after the next, and it's very intuitive for us to think about. But it also allows us to do Node's evented style, and if we wanted to, if the problem fits, then we can also do parallel computing with threads. And the nice thing, but also the main drawback, is you can mix and match these paradigms. So we'll see that in a few slides. So in order to get us going with registering for events, the first thing we need is a reactor. So unlike Node, because we can do procedural and evented programming side by side, you have to explicitly start your own reactor. And in Ruby, a reactor is just a gem, and there's multiple choices out there, but Event Machine is the most widely used, and the way you start a reactor is you call em.run and you pass it a block. Everything inside that block is going to be running inside the reactor. You can manually start the reactor, but if you use one of these app servers that are reactor aware, then you already have a reactor running for all of your application code. So if you use thin, then everything in your Rails process is already running within a reactor. So once we have the reactor running, we need to subscribe for events that we care about. So in order for our reactor to have something to do, I put up this example, which is fetching a web page. And what's happening here is first you build a request object and then you register a callback for when that request is finished. Unfortunately, this Ruby version looks a lot like the node code I put up earlier, because here, even though we're just trying to fetch a web page, we're still handling blocking IO by ourselves. And the problem I see with evented code is it doesn't really matter what language or framework you're using. You're always going to have to kind of deal with this callback inverted control pattern. And I think this is actually worse than the node version because now you're writing Ruby, but it doesn't look like Ruby anymore. It's valid syntax, but it's not natural feeling. So what we really want to have is we want to get back to a procedural style syntax but have the executing code run in an evented fashion under the hood. So ideally, this is what we'd want the code to look like. And even if you didn't know what Faraday does as a library, you can kind of guess what this does, right? You're fetching a page and the result is going to be saved in the response. The nice thing is Ruby lets us hide blocking IO and related callbacks into library code. So Faraday gives you the option to kind of switch out adapters for how you actually do the page fetches. And if you just switch it to the event machine synchrony adapter, then suddenly it hides all the system event callbacks into the library. So even though when you do the page fetch, it's going to run in that evented fashion, when you're coding at the application layer, it's going to be this intuitive style that you're used to. And the way this is actually done is we use Ruby fibers. And fibers are primitives for implementing lightweight cooperative concurrency. So basically that means you can create code blocks that can be paused and resumed, like threads, but the main difference is they aren't preempted and the scheduling has to be done by the programmer. So a fiber is a crow routine, which means that we have to manually control which fiber is running, only one fiber is running at a time, but we basically have to pause when we expect blocking IO to happen. So we start the first request. When we know there's going to be blocking IO, we pause the request and yield the control back to the reactor. And when data is ready to actually finish the request, then we resume the request fiber. So this still kind of sucks because, you know, it's the same thing we've been talking about this whole time where human beings are just not good at scheduling as an operating system or a computer is. So at some point we're going to screw up and we're going to forget to yield control back. So we have a reactor in one fiber, but we still need to have requests in their own fibers so we can kind of do the switching back and forth. And the nice thing is that web requests are naturally independent of one another, right? So... Lost my place here. So to wrap each request in its own fiber, there's a rack middleware out there called Rack Fiber Pool. And because Rails is rack, it's really easy to configure your stack to do this. You just basically add it to the top of the middleware stack. And if you're using any other web frameworks like Sinatra or Grape, you can also use the same gem. So so far what we've done is we've chosen a reactor for our app server, we've wrapped each request in its own fiber, and we haven't changed any of our application code. If you benchmark your app at this point, you're going to be really disappointed because everything's still going to suck and be slow. And the reason for that is because the Ruby ecosystem wasn't written with Invented in Mind, all of your application code is going to be blocking all the time. So the reactor is going to come in, start the first request, but as soon as there's a blocking library, it's not going to yield so the reactor can't actually do anything when the second request comes in because it's already blocked. So basically what we're looking at is the same process concurrency as before. But really that's okay because we're not doing any worse than when we started, right? So we're still using process concurrency. Each of these Rails processes is still handling one request per process. And we can only do better from there. So some of the starting points of where to start unblocking your reactor is to take a look at your data stores, external HTTP calls, and system calls. For data stores, a lot of the drivers for these data stores will either support Event Machine out of the box and you just have to configure it or there'll be special adapters available for them. For HTTP, you can use Fairday, there's a built-in adapter. And for system calls, Event Machine comes bundled with its own version of kernel.popen, that's non-blocking. And if you really can't rewrite a chunk of code to be evented then what you can do is call em.defer and what this will do is kick off another thread, run the block in a separate thread and when that returns, feed the results back into your main reactor loop. So after we've tweaked our libraries to be reactor aware we're getting an execution pattern that's much more similar to what we're seeing unknown. Why and when should you use Ruby? If you do spend some time trying to add evented IOT or Rails app you'll see some clear benefits, right? You can reuse your existing code. The performance won't be worse than when you started. You can keep that same pretty Ruby readability that we all appreciate and it's also multi-paradigm so you can kind of pick and choose what things you want to optimize with what style of programming. Why should you use Node? The nice thing with Node, I think the single biggest win is because everything is in this evented paradigm, everything is very consistent so every library you go out and find is just going to work, right? It's going to be aware that it has to register callbacks whenever there's blocking IOT because there's no alternative and the community is much better than the Ruby evented community at this point so if you have a question or get stuck you're much more likely to get help. So while it's straightforward to write a utility script doing evented IOT in either Ruby or Java script it's much harder to kind of go and rewrite your app in another framework and language so please don't do that. Whether you're already on Rails and you want to switch to something else or on Java script and switching to Rails, it's just a bad idea. Just don't do it. But for certain things you can still write specific features that fits this evented paradigm so you can write one feature and have it either run alongside your Rails system and just route requests between them or... Yeah, so I'll give an example of that guy. So on GitHub all of our .com pages are going to be rendered through Rails so we're not enjoying the benefits of evented programming but at the same time as long as the site feels fast that's all you should really care about and whenever you see this zip button when you download a zip archive of a Git repository that's actually a node process that handles that so the node process is living alongside of the Rails process and whenever it sees this request we route the user to have the node process generate the zip archive on live and send it out. So wrapping up a bit, evented programming is going to be tricky in any language you choose. It really isn't a matter of choosing between Ruby or JavaScript. You should try to aim evented programming for the types of problems where it makes sense. It also doesn't make your requests magically faster so I think it's more important to make sure that your response times are reasonable before you even try to dabble in this. And finally, I think for the trade-off in maintenance and readability I think even if you use evented programming you should hide the IO logic with libraries that actually deal with IO. You should never pollute your domain and business logic with these callbacks about what your database is doing. So if you take some time and start tinkering some of these concepts in a separate branch then you can kind of benchmark your app and kind of see which changes are worthwhile and which changes are just more hassle than they're worth. Cool. Thanks. Questions? One of the things you said was to hide the eventedness of the programming in libraries. And you mentioned Faraday example. Doesn't that mean, though, that you can only juggle like events, like blocking IO against one another? That is, if you have a whole bunch of HTTP in the tweet example you can have seven tweet network requests but you can't juggle those against the disk requests, right? Because those aren't going to be the same underlying net physical. Okay. So the question is, if you juggle HTTP events suddenly you're tied to just dealing with HTTP events, right? Really the main thing you need to optimize and look out for is to make sure you don't block the reactor. So as long as the reactor is not blocked it can dispatch on arbitrary events. So if your database adapter is reactor aware and your HTTP library is reactor aware whenever, you know, if your HTTP call is busy but there's an incoming DB request that can be executed while the first fiber is busy. So I don't quite follow the question. Does using then give you advantages? So the thing with using any of these technologies is even if you have the reactor and you start it up it's going to handle one request at a time because all of your code isn't reactor aware. So you never yield control back up to thin, back to the reactor for it to do something else. So the nice thing is you basically get to pick and choose where you want to optimize things. So if you notice there's a big chunk blocking here then you can optimize just that one chunk of code. Cool. All right, thanks guys.