It’s an excellent sales pitch for Rails, but is it true?
As much as we might like it to be, the sad truth is that, if your goal is to design high performance scalable websites, there is still much to be learned beyond the syntax of a programming language. Nothing comes for free. Of course, this argument for the need for thorough training in software engineering principles applies to all languages equally. But does using Ruby on Rails rather than some other application language and framework significantly reduce the topics you need to master to be a great application developer?
Rails is redefining the landscape of web development. Ruby is a wonderful programming language, and the Rails framework does dramatically increase productivity. However, databases, legacy systems, and third parties who don’t share our love of Rails are a fact of life. Even though Rails does, at times, seem downright magical, it does not make the rest of the stack obsolete.
The Tale of Twitter
Java, with its long (and constantly improving) track record, is now—along with its Microsoft twin .NET—the de facto enterprise language. But there was a time when you could brew a pot of coffee while the Java runtime environment booted, and enjoy each sip of your cup of joe while the hits flipped hither and thither.
So much research has gone into the JRE that it is now blazingly fast. Having shed its perception as a slow language, a rewrite to Java is often the first recommendation new management or venture capitalists might suggest when introduced to your project. Imagine if Java had been perceived the way Ruby on Rails sometimes is today. It would have been amazing if one poorly designed Java application had convinced all technology decision-makers that Java itself was a bad platform for developing applications. In fact, much of the emphasis on Java’s speed was likely a result of early failures that were as much due to scaling or design problems as they were speed-related. Now, with Java’s speed on par with or faster than that of C++, it’s the developer who becomes immediately suspect when an application underperforms, rather than Java itself, or the framework being used.
Twitter has become the whipping boy for Rails’s perceived scalability problems. Twitter is a new twist on the everyday blogging platform. Unlike a standard blog, posts to Twitter, or “tweets,” are limited in length. They can be written online in a web browser, but are more commonly written via SMS text message, or from a variety of third-party applets. Subscribers, or “followers” of your Twitter blog can read your tweets in the traditional way online or via RSS, but more commonly subscribers receive your posts in realtime via SMS messages to their phone. In essence, Twitter is a messaging service, brokering many-to-many communication. Although the predominant use has been individuals keeping up to date on what their friends are up to, Twitter “streams” have also been used to spread messages quickly to conference participants, or to spread other types of topic-based messaging to interested parties.
Like many companies before them, Twitter encountered problems when their success exceeded their expectations. A successful viral marketing company, their site’s user base quickly jumped into the millions of users, but the site couldn’t handle them. The result was slow page load times and, at times, outages.
This type of problem is one of scaling: you’re doing perfectly fine until the demand for your site suddenly increases. Scaling issues suggest that you’ve achieved some level of success (what a silver lining!), but it’s the type of problem you’d like to avoid, lest you become the whipping boy, or worse, lose all of your users to a competitor.
But Ruby on Rails was not the culprit in Twitter’s scaling problems, a fact Twitter engineers reiterated on many occasions. It was the architecture that was at fault, and architecture has to do with how you structure data and applications and how they communicate, not what language the applications are written in.
Because Twitter was the largest, most public Rails site around, its stumbles were watched carefully and the steps Twitter took to alleviate its scalability issues were thoroughly documented online. In one instance, the database was becoming a bottleneck. In response, Twitter added a 16GB caching layer using Memcache to allow them to scale horizontally. Still, many queries involving complex joins were too slow. In response, the Twitter team started storing denormalized versions of the data for faster access. In a third instance, Twitter found its use of DRb, a mechanism for remote method invocation (RMI) had created a fragile single point of failure. They replaced DRb with Starling, a distriuted messaging queue that gave them looser coupline of message producers and consumers, and better fault tolerance. After these and other architectural improvements, Blaine Cook, Twitter’s lead architect, said:
For us, it’s really about scaling horizontally – to that end, Rails and Ruby haven’t been stumbling blocks, compared to any other language or framewor. The performance boosts associated with a “faster” language would give us a 10-20% improvement, but thans to architectural changes that Ruby and Rails happily accommodated, Twitter is 10,000% faster than it was in January.
It is of no small significance that Twitter’s engineers chose to absolve Rails of being at fault for their problems; instead of offloading the blame to an external factor, they chose instead to take responsibility for their own design decisions. In fact, this was a wise choice. Twitter’s engineers knew that re-implementing the same architecture in a different language would have led to the same result of site outages and site sluggishness. But online rumor mills were abuzz with hints that Twitter was planning to dump Ruby and Rails as a platform. Twitter’s co-founder, Evan Williams, posted a tweet (shown in Figure 1) to assure everyone that Twitter had “no plans to abandon RoR.”
Speed Versus Scalability
It is true that Ruby, as a language, does execute software programs more slowly than some other programming languages. However, this is a red herring in the discussion of scaling websites, as speed and scalability are not equivalent. If the stuff of websites was real-time processing of complex data, then Ruby’s speed could be problematic. However, most websites don’t do much more per request than look up some information and display it, or otherwise accept some information and store it. The time required to process this type of task is fast no matter what language you choose.
On the other hand, once you start building up simultaneous requests, these requests will compete with each other, and requests will begin to appear slow to end users. In an ideal situation, you would simply add more hardware to restore your site to optimal performance levels. In fact, in an architecture designed for scaling, you should be able to add hardware in a linear fashion to handle any number of users imaginable.
This is where speed and scalability get confused. A language may be slow or fast, but it’s your architecture that is scalable or not. This pertains not just to your code itself –, the algorithms and how you string together your syntax – but also on the edges of your applications, how they fit together, and the expectations and demands placed on different parts of the system. Are any spots likely to become bottlenecks or single points of failure? Can each piece improve its own scalability with the addition of hardware, and if not, can that piece be removed from the critical path of rendering web pages? If your architecture is not designed for scaling, you may not be able to simply add hardware to scale up for additional users. And while a faster language may buy you some more time, no language can avoid the scalability issues of a poorly designed architecture forever.
Unfortunately, architectural problems are so fundamental to how an application is written that it is nearly impossible to rescue a bad design once it has been implemented. Band-aid solutions may work for a while, but scalability problems are usually widespread and entrenched. It’s like plugging holes in a leaking dam; eventually the dam will give beneath the pressure of the water, regardless of how many patches have been applied. The only way to be confident your scaling efforts will work is to design for scale from the beginning.
So why don’t wouldn’t developers plan for scaling from the beginning? One reason is that they don’t know how. Most books in the bookstore, intended for as wide an audience as possible, frequently don’t get past syntax. In. this book, design is a major and repeated theme. Another reason is that they believe too much up-front design will slow them down. This may be true, but it certainly becomes less and less so as you get the hang of it. The final reason is that many leaders of the Rails community itself have advocated not worrying about scaling until you really, really, really need to. They say it’s an unnecessary waste of time up -front and that Rails scales easily because it’s a share-nothing architecture. This, of course, is baloney. In the early days this was good propaganda to get Rails onto developers’ desktops, but today it is simply hurting Rails’ image as an option for enterprise deployment. As Twitter proved, waiting until you really, really, really need to worry about scaling is too late to start worrying about scaling.
Of course, you don’t always have the choice of starting your project from scratch. You may be reading this book when you’re well into a project and are looking for tips, or perhaps even later in the game; you might be trying to save a poorly designed project that’s already straining under the pressure of load. To help readers in these scenarios, many chapters contains a section called Refactor Steps, intended to give you step by instructions on how to transition an existing design to the one described in the chapter.
What to Expect in This Book
If you are new to Rails, the first book to read is Agile Web Development with Rails by Dave Thomas, David Hansson, Leon Breedt, and Mike Clark (Pragmatic Bookshelf). The book you have in your hands, by contrast, is not a how-to guide for writing for your first Rails application. This should be the second book you read.
This book deals in larger concepts, the formulas for how pieces fit together. It is not a compendium of the Rails API or a reference of the Ruby programming language. Books on these topics exist, and are good to keep on your bookshelf, but they contain descriptions of tools rather than a formula for putting those tools together to get your job done.
This book gives you the tools to develop applications for the enterprise world, for websites with global scale. Scaling comes in two forms. One is the scaling we traditionally think of in terms of handling thousands, hundreds of thousands, or even millions of users, typified by Twitter and other large scale websites like Google, Amazon, or Ebay. The other type of scaling is a more practical, human-focused concern. As your business needs change or expand, and as the types of developers in your organization and their quantities increase, will each developer still have the ability to contribute to the product in a meaningful way? Can new features be added easily and in parallel without conflict, or is the application difficult to modify by multiple developers at once? Will you be able to harness the hard work of the past in building the future, or will each new bold direction require a rewrite of the entire application?
Scaling of both varieties is facilitated through careful design of your application’s software architecture, rather than through the choice of language or platform. It’s a common misconception that scaling problems will be solved by the materialization of a faster Ruby interpreter, or by learning a magical set of Ruby incantations that aren’t described in the beginner books. Certainly, there are good and bad ways to describe any algorithm, but these are problems solved by comprehensive computer science training, not by the speed of the interpreter. Choosing a bad implementation for an algorithm will have similarly poor results in Ruby as in Java or Perl or otherwise.
The purpose of the Ruby interpreter and the Rails framework is to give you a tableau on which to develop your masterpiece. That’s where this book comes in. This book is about the principles involved in architecting serious web applications. The principles are universal, regardless of what technology you are using in the application tier. Of course, as you may have guessed, in this particular book on web software architecture, Ruby and Ruby on Rails will be used to describe all of these principles.
As it happens, Ruby is a terrific language, with many advanced features not found in today’s compiled languages. Not only is Ruby feature-rich, but it is also succinct to the point of marvel. What often takes dozens of lines of code in Java can often be written in just a few lines in Ruby. Rails, too, is a best-of-breed platform for developing web applications with little overhead. The commonly touted benchmark is that Ruby on Rails development proceeds at a clip of 10 times the rate of Java development. Big names like yellowpages.com have invested a lot of time and effort (and money) into rebuilding their entire sites on Ruby on Rails for the long-term benefits they will reap down the road in having a simpler and more efficient (by metric of code volume) framework.
However, there is a problem with this benchmark, and with the ethos of many in the Rails community as it exists today. Because so much effort has been put toward showing how simple it is to develop with Rails, and how much more productive you can be than with Java or other alternatives, little effort has been put toward showing Rails developers how to build applications that can truly stand up to the challenges that their Java cousins have had to prove themselves worthy against.
This doesn’t mean that a Rails application cannot stand up to the challenges of large scale and constant pounding by endless traffic. Of course Rails can. However, there has been a dearth of public examples, and by the same token, there has not been much public discourse within the Rails community about how to design Rails applications to scale to the same levels as have been achieved by Java applications.
The secret is that the principles are the same. They were even the same before Java was de facto. The difference is that in Rails, with Ruby, the principles are so much easier to achieve once you know what you are doing. Because Ruby is so succinct, describing how to achieve the goals of good software architecture for the web is almost invisible when written in Ruby code.
Whether or not the revered “Gang of Four” Designs Patterns needed to be explicitly retooled and retold for a Ruby audience has received its share of debate; achieving patterns is trivial in Ruby even though they required intricate structing of code in Java and C+. Indeed, the singleton pattern is achieved in Ruby by saying include Singleton. The factory pattern is so simply reproduced it barely warrants a name.
Most of the effort of architecting in Ruby is not found in tens of thousands of lines of application code. Instead, it’s in how you use Ruby and Rails to tie together all the other parts of your application stack: the database, your servers and clients, other services, and users of your application.
In this book, we begin by putting Rails in the correct context. The original Rails book, Agile Web Development with Rails, had as its purpose selling Rails to the world, so the viewpoint is somewhat myopic. From its perspective, Rails may as well be the only element in the stack. The database is obligatory, so magical migrations are created to hand wave it away and ensure you never need to learn a scrap of DDL; anything else is pejoratively labeled as “legacy” and ignored. In the real world, databases and legacy systems tend to outlive everything else, hence the seemingly inescapable term “legacy” itself, so it’s worth paying them their due.
This book introduces “architecture” for enterprise web applications, from the ground up. What are the topics of web architecture, and why aren’t they found in most books on Rails? In truth, the success or failure of a web application has only partially to do with what is classically called the application layer. This can come as a surprise to those who have cut their development teeth on Rails, because the Rails view is that the application layer is all there is. It turns out that it’s the edges around the application that can make the biggest difference: databases, caches, and in a service-oriented architecture, the constellation of back-end services and front-end websites that make up the entire application.
First, below your application is the database. A schema stays with you for a very long time, so how you structure your data determines whether you can guarantee the integrity of your data, and whether your queries will be fast or slow. How those queries are written makes a big difference too, meaning you need to understand SQL even if you are using an object-relational-mapper like ActiveRecord. To write an application that is fast, you need to know into which queries a set of ActiveRecord statements will translate so that you can issue your queries in an intelligent way. “It works” usually is not enough for an enterprise application; as a developer you need to know how it works, how it should work, and why each way is as it is.
If you plan to avoid the hassles of optimizing a database schema and writing optimized queries by caching query results or rendered pages, be prepared for difficult times ahead if you want speed and consistency at the same time. It’s easy to make a cache that returns old, stale, invalid data. Correctly implementing a cache that is up to date in real-time is no simple task. What goes in the cache should be chosen carefully, as well as in what format. And the most difficult challenge still remains: when and how to invalidate or rebuild elements in the cache. Many people naively treat caching as a trivial problem, but depending on a cache that is out of sync with reality can be far worse than a slow site. Relying on stale data can lead you to make incorrect decisions, sell products you don’t have, double-book a flight, or not sell products you do have because the cache doesn’t know about them.
Once you have mastered the above areas, suddenly the problems are raised an order of magnitude. It’s the rare website that is powered by a single monolithic application with a single database. To scale, not only to handle ever more users, but also to handle application and organizational complexity, a service-oriented architecture (SOA) is almost always a necessary architectural evolution. In SOA, many applications are responsible for different slices of the overall problem. How do you choose how to split up a monolithic application, and further, how to you glue the pieces back together to give a site’s visitors a unified experience?
In this book, we’ll cover these topics in detail. Of course, the application layer itself is extremely important, so we’ll start there, with the proper way to think about and structure your application. We’ll see how and when to separate code into logical elements, called modules. Then we’ll extract code into plug-ins to be shared by multiple applications. In many books, these topics are treated as advanced topics; in this book they come first so you will actually have an opportunity to use them before you get entrenched in a design.
After looking within, we’ll look downward to the database layer. We’ll see how to build a solid foundation for our application with proper data modeling. First we’ll learn about referential integrity and database constraints, culminating in writing trigger-based stored procedures to ensure complex relationships are satisfied. Then we’ll discuss rigorous levels of database normalization, including third normal form and domain-key/normal-form, which will help us ensure our data’s integrity.
We’ll introduce the concept of domain tables, and how these special tables can be incorporated naturally in Rails. We’ll see how to base Rails models on database views. After that, we’ll get our first taste of caching by materializing a database view, increasing database performance by orders of magnitude.
Next we’ll look to the sides as we explore service-oriented architecture. An oft-misunderstood concept, we’ll spend a good deal of time concentrating on theory. Then we’ll build multiple RPC-based backend services to be consumed by a thin frontend client. We’ll build a REST web-service, too, but we’ll see how to build any type of REST service, not just the subset supported by ActiveResource.
Finally, we’ll revisit caching, treating it like the sleeping monster it really can be, giving you the tools to ensure with certainty that the caches you create can be depended upon to be accurate.
Throughout the book, a heavy emphasis will be placed on testing, both unit tests within an application and integration tests when we connect multiple applications together. In fact, in this book we never create a single view or traditional controller. Instead, we exercise the model classes we write with our tests.
How This Book Is Organized
In Chapter 1, we start out by taking a tour of an ideal enterprise systems layout, highlighting all of the elements that aren’t Rails, as well as noting the various places where Rails can fit in.
The rest of this book can be divided principally into three major sections. The first deals with the Rails framework itself. The next few chapters examine Rails itself. While Rails gives you a Model-View-Controller (MVC) framework to start your projects, there is much to be desired in terms of structuring and maintaining large applications. Chapters 2 and 3 fill these gaps. First, Chapter 2 dives into plug-ins, and how they can be used to improve application clarity, while also encouraging you to write reusable code. Chapter 3 introduces modules: what they are, and how and when to use them.
The next major section of the book, Chapters 4 through 12, deals with the database. The database layer has not really been given its due in the Rails community, and Chapter 4 begins by providing an overview of why it is such a critical part of your application. In Chapter 5, we start building a data model for the example application we’ll work with throughout this book: a sales website for movie tickets.
Although the schema we’ll design would be sufficient for most Rails books, we’ll see quickly that it was a naïve design. In Chapter 6, we refactor the schema to be in third normal form (3NF). In Chapter 7, we’ll pick out a special type of table called a domain table, and we’ll incorporate these tables naturally into a Rails application. In Chapter 8, we’ll expose some more problems with our schema, then tighten it one step further by moving from third normal form to domain-key normal form. In Chapter 9, we’ll get even more advanced with our introduction of stored procedures and triggers. We’ll use them to enforce relationships that built-in database constraints cannot handle, giving you the power to completely lock down your database schema.
Chapters 10 through 12 introduce some new database-related features to Rails developers. In Chapter 10, you’ll see how to base an ActiveRecord model on a database view. This is useful for automatic filtering, or for filtering on data that’s not easily available at the application layer. In Chapter 11, we’ll show how to build support for multiple table inheritance in Rails. Rails supports single table inheritance, but it is not always the right tool for the job. In Chapter 12, we’ll get our first taste of caching when we materialize the view we created in Chapter 10, giving our view a huge boost in performance.
Chapters 13 through 18 deal with service-oriented architectures and different techniques for connecting systems together. In Chapter 13, we start by defining SOA, as well as going over the scenarios when it is the right choice to solve a problem. Chapter 14 covers considerations that go into designing a service-oriented architecture, including guidelines for designing an API. Chapter 15 is a critical exploration of REST, helpful for placing it in its proper context for web-services, but not necessarily for service-oriented architectures. In Chapters 16, we build our first SOA service. In Chapter 17, we build upon our accomplishments in Chapter 17, connecting two backend services together and testing them with the same interface a thin front-end client would use.
Chapter 18 is a culmination of many of the previous chapters. There, we implement a fully correct service-layer cache to enhance application performance. We also go over other places where caching can be a dependable way to improve performance.
Who Is This Book for?
The purpose of this book is to provide the background you need to build your bridge: a large-scale, enterprise website. It is assumed that you’ve already read the manuals of the tools you need to get the job done, e.g., books on the syntax of Ruby and of Rails. This book fills in the background that transforms you from a layman who has tools into an expert ready to make the most of those tools.
This book is geared toward three general types, which we’ll call the Student, the Glass Ceiling, and the Travelers.
For the Student, this book is full of the theory behind engineering large-scale enterprise web applications, so if you are embarking upon a class on software engineering for the web, this book is for you. This may even be your textbook, and if so, Mazel Tov! You will learn a lot here.
Our Glass Ceiling audience is those who have read half a dozen manuals about their tools and toolkits, but still find they don’t have the background to jump to the next level of web application design. These are the so-called “newbies.” The tools are beginning to make sense, but how to use them together effectively may still seem murky to this audience. If you are in this group, this book is for you, too. Soon all the pieces will be dovetailing nicely.
The final group that may benefit from this book are Travelers. Travelers may have a lot of experience with other frameworks, but they are wondering how to make it all go smoothly with Ruby on Rails. For this audience, this book is a great refresher in all the basic theory behind solid web application design, followed by a hearty dose of how to integrate all that theory into Rails.
|Chapter 1 : The Big Picture