Talks

A Polygot Heroku

A Polygot Heroku

by Terrance Lee

In the session titled "A Polygot Heroku" at Rails Conf 2012, Terrance Lee, Senior Ruby Engineer at Heroku, discussed the platform's significant evolution towards polyglot architecture over the past year. Heroku now supports various programming languages such as Java, Clojure, Python, Node.js, and Scala in addition to its roots in Ruby.

Key points from the talk include:

  • Platform Changes: The introduction of a new stack called Cedar, which provided greater flexibility over the previous Bamboo stack. Cedar allows developers to specify multiple process types easily, enhancing the deployment experience.
  • Improved Logging and Process Management: Cedar also introduced a new logging system that improves log aggregation and readability for developers. This system allows for better management of logs by process type, aiding in debugging and monitoring apps.
  • Custom Build Packs: The creation of build packs enables the customization of the build process for different programming languages. An example illustrated the creation of an NES emulator build pack, showcasing how developers can create unique applications by leveraging Cedar's capabilities.
  • Ruby Enhancements: Lee highlighted improvements made for Ruby developers, including a more efficient bundler and the future support for multiple Ruby versions on Heroku. The ability to specify Ruby versions in the Gemfile is on the horizon, enhancing the development workflow.
  • Community Engagement: The talk also emphasized Heroku's commitment to investing in the Ruby community by hiring dedicated engineers to work on Ruby's advancement, aiming to support it long-term.
  • Future Projects: Discussions of upcoming features such as Ruby 3 support, and enhancements to the Rescue task processing library were mentioned, indicating ongoing progress and community contributions.

Towards the end, there was a Q&A session covering additional topics such as process handling and DNS management, furthering the discussion on Heroku's current capabilities and future developments. The overall takeaway is the significant strides Heroku is making to improve developer experience across multiple languages while maintaining a solid focus on Ruby improvements.

00:00:25.560 All right, so my talk is about a polyglot Heroku. I'm Terrance Lee, and I am the Senior Ruby Engineer at Heroku. That basically means I run the Ruby experience on Heroku. When you push an app that is Ruby-related, the entire process related to Ruby is my responsibility. I ensure that frameworks like Rails work properly on the platform. You can find me on Twitter at @TerranceLee, GitHub at github.com/terencelee, or email me at [email protected].
00:01:07.759 One of the first things I want to talk about is that last year we announced a new stack in beta called Cedar, which brought a lot of major changes compared to Bamboo. Cedar added a lot more flexibility to the platform. One of the major updates was the new way to specify process types. Before Cedar, you were limited to this Dyno worker model, which only allowed you to run a worker that executed rake tasks and background jobs. To run something like Rescue, you had to create an alias in your Rake file. For the web process on Bamboo, you were restricted to a single Thin server that we ran for you.
00:01:52.240 On Cedar, you can now specify all the different process types you want by simply listing the process type name followed by a colon and the command that should be run. For example, if you want to run a simple Mongrel server, you would use the command 'bundle exec mongrel'. Additionally, you can specify different types of workers like Rescue workers and set up cron processes. This makes it quite easy to define multiple process types.
00:02:42.599 Prior to Cedar, we also faced issues with logging. One of the complaints a few years ago was that obtaining logs was difficult, as it only displayed the last hundred lines or so. We realized the need for a better way to aggregate logs, so we implemented a new logging system that is now enabled on every app. You can stream your logs by using 'heroku logs -t', and we treat logs as streams. Additionally, you can use the 'heroku ps' command to specify specific process types for which you want to see logs.
00:03:02.480 In Cedar, we also improved handling for common commands. In most Rails apps, you typically need to run 'rake db:migrate' to migrate your database. There's a special hook in place to allow you to run 'heroku rake db:migrate'. Previously, to run a Rails console, there was a middleware that was injected at compile time that allowed HTTP requests to pass through to your Dyno. This meant you could alter environments and configurations live, which was problematic. Now on Cedar, we have 'heroku run', which allows you to execute one-off processes that spin up an entirely separate Dyno isolated from your running processes. You can run a console, execute migrations, or even run a bash shell to explore your app.
00:04:36.560 One of the most noticeable changes in Cedar is that we have gone polyglot. Initially, we started supporting Node.js, followed by Java, Python, Clojure, Scala, and even Ruby. There has been some concern that supporting multiple languages fragments resources within the Ruby community, but there has been significant work done to improve the platform overall through this expansion. One outcome of this effort has been the development of buildpacks.
00:05:12.400 A buildpack is a language-specific component of the build process. Previously, we had a slug compiler, which generated a slug that was pushed to S3 and pulled down during Dyno boot. That slug contained all the necessary assets and dependencies. In the old Bamboo and Aspen days, Ruby code was integrated directly within the slug compiler. One of the first things I did was separate the Ruby components into their own buildpack. This separation made supporting additional languages, like Java, significantly easier.
00:06:06.000 The power of buildpacks allows for creative implementations. For example, I created a NES buildpack, where you simply provide a directory containing the ROM files. Create an empty Git repository, add your changes, make a commit, and then create a new Cedar app where you specify the buildpack. When you push, if we detect that buildpack URL, we fetch and execute that code instead of the standard Heroku buildpacks for Node, Java, Python, or Clojure. In this NES app case, the buildpack detects it and runs the NES application, allowing you to play games like Teenage Mutant Ninja Turtles on your browser.
00:08:15.399 Let's step back and look at a more basic buildpack I created, which is a no-op buildpack. This simple buildpack just takes your Git repository and turns it into a slug without any modification. It doesn't install dependencies or make any changes. Building a buildpack generally requires three steps. The first step is the detect phase, where we determine the name of your buildpack. In built-in Heroku buildpacks, there are mechanisms to determine if the buildpack should be used. However, for custom buildpacks, you'll need to define the name directly.
00:09:13.680 Next, during the compile phase, we don't perform any operations because we want to retain all files in the Git repository. Slug compiler handles a lot of the backend work before passing onto the buildpack, and we avoid modifying the Git history. Lastly, we have the release phase, where you specify commands to set up any add-ons, environment variables, or default process types during the initial push.
00:10:30.679 Now let's take a look at what happens during the Ruby buildpack. To determine if an app is a Ruby app, we check if there is a 'Gemfile' present. This allows us to differentiate between Ruby apps and those using other languages, such as Node or Clojure. For Rack applications, we check for the presence of 'config.ru', and in Rails 2, we look for 'config/environment.rb'. In Rails 3, we examine the application file to make sure the appropriate environment is defined.
00:13:34.639 The compile phase for Ruby is much more intricate than for the no-op buildpack. If a custom Ruby version is specified, we install that, set up the language pack environment, and configure paths to ensure everything is aligned correctly. Installing the appropriate gems is also critical, and we always install 'bundler' to ensure desired gem versions are available. Following that, we install any required dependencies and create a database configuration file if necessary.
00:14:54.000 One significant change in Cedar compared to Bamboo is that we no longer automatically install add-on plugins. Previously, we set up add-ons like New Relic automatically for new users. While this was beneficial for new developers, it posed challenges for more advanced users who wanted to customize configurations. Now we only provide environment variables and documentation, allowing developers more flexibility without unnecessary magic behind the scenes.
00:15:45.720 During the release phase, we set up the language pack environment again to ensure everything is prepared correctly. We check for Rails apps or if the PG gem is present to set up a shared database. Environmental configurations are handled here as well, including ensuring default process types are established depending on whether it's a Ruby, Rack, or Rails app. This new structure makes it easier to manage dependencies and updates.
00:17:08.320 Recent efforts at Heroku have also included significant investment in the Ruby community. Notably, we hired Matts, who is focused on improving Ruby directly. Additionally, we brought on Koichi, who contributes a wealth of experience, and Richard, our Ruby evangelist. Before these hires, there were zero full-time Ruby developers at Heroku, so now we have a dedicated team working to improve Ruby and our integration with it.
00:18:40.720 Out of this focus has come contributions to the Ruby ecosystem, such as the bundler 11 release that came out two months ago. We made a concerted effort to improve performance, particularly reducing the time it takes to install gems—previously around 18 seconds for a simple gem install. With bundler 11, we can now fetch from API endpoints, cutting that down to about three seconds.
00:19:40.679 We're not stopping at bundler 11. We're actively working on bundler 12, which includes features especially requested by developers for better local development. This includes features that allow gem developers to integrate seamlessly with local gem development, ensuring that changes can be easily deployed. One feature we're focusing on is multi-version Ruby support. Many developers want to know when they can use Ruby 3 on Heroku, and we're hoping to have this support available soon.
00:20:56.159 Alongside the development of bundler, I've also been focused on improving the Rescue library. There are misconceptions that Rescue is not actively maintained. We are indeed focused on ensuring that Rescue continues to evolve with features like New Q and multi-Q classes, plus support for threaded and forked consumers. These improvements are aimed at addressing memory usage during processing and enhancing performance for heavy I/O jobs.
00:22:50.120 This year, I've concentrated on making significant improvements in the code base to support various encoding formats, allowing different types of serialization to be implemented. This gives developers more control and flexibility over how jobs are managed in the queue. Within the implementation, we introduced features to use either a blocking pop or a non-blocking mechanism, making it easier and more efficient to retrieve jobs as they come in.
00:26:01.360 I wanted to keep this brief since it's the last session of the day. Let's open the floor for any questions about Heroku or general inquiries related to Ruby or Rails development. (audience questions and answers follow) For example, one question discussed managing signal responses with Unicorn and Nginx to ensure proper shutdown procedures for Dynos. Another audience member wanted clarification on how dynamic routing works within Heroku's infrastructure and the interaction with caching layers.