Talks

It's Rubies All The Way Down!

Recorded in June 2018 during https://2018.rubyparis.org in Paris. More talks at https://goo.gl/8egyWi

Paris.rb Conf 2018

00:00:21.720 Alright, so this is going to be fun because when I originally wrote this talk, it was a 40-minute talk. Every time I've tried to give it in under 20 minutes, it's come out a little different. It's just an adventure, and we'll see what happens. If there's something on a slide that I fly right over and you have questions about it, either ask me at the end or come find me because there are some things I'm going to fly through really quickly.
00:00:34.300 So, it's Rubies all the way down! What I'm really talking about here is both a historical look at how the modern Ruby web stack developed the way it did, and also looking at some ways that, in my own practice writing Ruby web apps, I've pushed the boundaries of some areas that weren't typically done with Ruby and how that worked out.
00:01:01.090 I started doing Ruby in 2001 and I created my first professional Ruby web app in 2002. Since then, I've done doodles and oodles of sites and apps, along with multiple server implementations. I currently work for Cookpad, so let's just dive right into it. Let me start my timer so that I have some idea of how far behind I am.
00:01:27.610 So, what is a stack? When I'm talking about a stack, I'm really discussing all of the components that go into allowing your web application to do whatever it needs to do. In a modern web application, there are usually a whole bunch of layers. The layers that I have up here are kind of the distillation of what you typically find in a web app. Some might have a few more, some might have a few less, but this is pretty typical.
00:01:47.320 Now, of these, there are only a few layers that are ever really done in Ruby. Obviously, you're writing your web application itself in Ruby. You may deploy with Capistrano, which is Ruby software. Some of you may have used FluentD for managing the logs of your entire application. You might also use Chef or maybe Itamae for your configuration management. However, most of those other layers, Ruby never touches. You use Varnish, you use Nginx, you use HAProxy, and things like that for those other layers.
00:02:07.630 Now, stepping back a little bit to the beginning, when I started with Ruby, I began with Ruby 1.6.6. I came to it from Perl, and at that time, in Perl, there were a lot of tools for web development. When I switched to Ruby, I really loved it and wanted to use Ruby for my next web development gig.
00:03:09.430 In 2002, I was supposed to write a job search application for college interns, so I said, 'Alright, let's do it.' What I found was that there were no real frameworks for web development in Ruby back then, and very few tools available. There was some basic templating with something called Amrita, and other than that, you were pretty much on your own.
00:03:27.290 You could use raw CGI or something called mod_ruby, which let you embed your Ruby interpreter inside Apache. This was even before Rails existed, so the Ruby stack barely existed; it was mostly CGI scripts or you stuck your stuff inside Apache. When I looked around to see what my options were, I came across something called Iowa, which was a proof-of-concept web framework written by a guy named Abigail Bryant.
00:03:52.330 Iowa wasn't fully suitable for production, but I adapted it to create my first web app, which I released using Iowa. The stack I had at that time looked somewhat modern in the sense that you had multiple processes talking via socket connections back to the web server, which happened to be Apache.
00:04:08.400 Fast-forward a couple of years, and Rails hit the Ruby world, transforming everything. The marketing behind Rails generated so much excitement that everybody adopted it and loved it, though it was still very Apache-centric around how it was deployed. Rails used something called FastCGI, allowing various processes to run externally and communicate with the web server via socket connections, but FastCGI had some serious scaling issues.
00:05:01.990 Despite these issues, the Rails world continued to rely on FastCGI and some variations for a couple of years. A couple years later, a new player emerged called Mongrel. Zed Shaw sent an email saying he had written this web server that was pretty fast and encouraged people to take a look at it. The HTTP parser in Mongrel was mostly written in C, but everything else was in Ruby, and it quickly became the preferred way to deploy your Rails applications.
00:05:21.200 At that point, the Ruby web stack as a whole began to evolve. You saw the adoption of other web servers like Nginx, and over time, things shifted away from Apache and primarily towards Nginx in the Ruby world.
00:05:40.460 In my own isolated world, I was writing tons of apps for years, and I never adopted Rails because it was a slow, resource-intensive tool that didn't work economically for what I was doing at the time. I stuck with Iowa because it was fast and light for my needs. Over time, I developed various ways of deploying my Iowa apps. Along the way, I created something that wasn't Rack but had similar goals to be agnostic.
00:06:02.660 Some of that original code eventually got pulled into the original Rack, which surfaced in 2007. Rack really provided the Rails world with an agnostic way to interface between web servers, middlewares, and your applications. This enabled developers to have components that could work seamlessly together without worrying about the low-level details of interfacing.
00:06:32.070 Iowa had excellent state management capabilities, which brought information from the web browser into your application seamlessly. It handled a lot of the form data and variable management automatically, so developers didn't have to worry about these low-level issues. However, a downside was that this session state was stored in a single process. Once a session began, any requests for that session had to route back to the same process on the same machine without any good way to balance the load.
00:06:49.860 In the 2007-2008 timeframe, I thought it was necessary to write a solution that could handle this session routing better in Ruby. When I discussed this with peers, the feedback I got was that you can't do that in Ruby; Ruby is too slow. Instead, I ignored that feedback and pushed forward.
00:07:11.310 As it turned out, the times I recorded showed that it wasn't slow. Some benchmarks I performed around that time demonstrated that I could handle about 15,000 requests a second on commodity hardware from 2008. While it didn't offer all the functionality of Nginx, it was surprisingly fast for what it did.
00:07:24.700 Rack later gained support for Swift around version 0.9. With that, I built a simple, all-Ruby stack that could be utilized for both my Iowa apps and Rails applications. By 2009, my stack had evolved significantly, leading to over a hundred production web apps running successfully.
00:08:00.520 Managing logs was a challenge when distributed across multiple processes and machines, prompting me to think about a solution in Ruby that allowed me to consolidate logs from all those apps. My solution had to be fast enough so that the app performance wouldn't be noticeably affected. I wanted it to be API compatible with the Ruby logger and accepted that I might lose a message or two if the logging process went down since the server was unlikely to go down frequently.
00:08:43.950 Even back in the Ruby 1.6-1.8 timeframe, I found that Ruby was sufficient for my needs. The architecture I designed is still operational today in various financial websites and other applications, including critical workflows for major US banks. These applications have been running for years without major issues.
00:09:15.180 The next question was what other areas of this stack that weren't typically touched with Ruby could I reasonably tackle? I'd like to explore a few of those possibilities in the last few minutes of this talk. One notable library in Ruby is IO::Proxy, which allows the creation of event-driven network applications. It wraps event-driven code in a DSL that enables the writing of fast proxies with relatively simple code.
00:09:54.380 Another area that's often overlooked is 'Webrick.' There's a lot of functionality bundled inside Webrick. It even allows you to set up an HTTP proxy in just a few lines of code. There's a link that provides an example of a simple HTTP proxy implemented using Webrick. Interestingly, asset caching is another area where Ruby can come into play. Although tools like Varnish are common, there are scenarios where you can implement basic cache logic effectively in Ruby.
00:10:35.230 I set up a simple proxying system using Puma combined with Rack to test how fast it could handle requests. Surprisingly, the system can efficiently proxy requests at a solid rate. Though it might not be production-ready, it demonstrates how capable Ruby can be for these tasks. In fact, I was able to achieve about 1800 requests per second on a low-cost server.
00:11:24.900 Another amusing experiment was creating a simple HTTP server to handle static assets, which turned out to be more fun than practical! Webrick, being a well-optimized server, can handle around 2700 requests a second on modern ram. The same setup with Puma returned about 3000 requests per second, slightly faster than Webrick while still performing respectably.
00:12:06.190 It's evident that Ruby can handle a surprising amount of functionality within web applications while remaining performant. While many assume Ruby is not capable of certain tasks, through smart design choices focused on performance, it can prove otherwise.
00:12:51.240 I haven't found any Ruby implementations for building a full database yet, but I'm experimenting with combining Ruby with lightweight schemes to tackle creating a distributed SQL database, which remains a work in progress. Interestingly, there has been a significant work done in creating a key-value store in Ruby called Roma. It’s a Memcache-compatible solution that allows easy implementations while still being straightforward enough to modify.
00:13:40.930 Ultimately, as evidenced by my experience with Capistrano (a deployment tool written in Ruby), I wanted to explore building my deployment setup in Ruby. It didn't take long to create a simple DSL to manage multiple servers within your cluster and automate the deployment using `git push` commands. Through the integration of Git and my new tools, the deployment process became more optimal.
00:14:58.490 In conclusion, there are endless possibilities of using Ruby for tasks that are usually considered beyond its capacity. It's essential to focus on targeting the development goals that align with Ruby's strengths. My experiences with deploying high-level applications in Ruby for several years have showcased its capabilities. Does anyone have any questions?