Talks

Multitenancy with Rails

Multitenancy with Rails

by Ryan Bigg

The video titled "Multitenancy with Rails," presented by Ryan Bigg at Rails Pacific 2014, delves into the concept of implementing multi-tenancy in Ruby on Rails applications. The speaker begins with an introduction of himself and his affiliation with Lifx, briefly mentioning the company’s smart light bulbs, before diving into the core topic of multi-tenancy.

Key Points Discussed:

- Definition of Multi-tenancy:

- Multi-tenancy allows multiple customers to operate on the same application while maintaining the appearance of separate applications.

- Bigg provides examples such as GitHub and Tumblr to illustrate how multi-tenancy is implemented in well-known platforms.

  • Important Tools and Gems:

    • Bigg discusses several Ruby gems available for handling multi-tenancy, including Apartment and AccessTenant.
    • He emphasizes the significance of documentation and proper maintenance when choosing which gem to incorporate into a project.
  • Using PostgreSQL Schemas:

    • The presentation highlights using PostgreSQL schemas as a solution for multi-tenancy, allowing clean separation of user data.
    • With schemas, each tenant can have its own set of tables, simplifying data management and enhancing security.
  • Challenges with Heroku Deployment:

    • Bigg draws attention to Heroku's documentation, outlining potential issues with using multiple schemas, emphasizing that performance can degrade significantly as the number of schemas increases.
  • Custom Solutions and Middleware:

    • As the talk progresses, Bigg mentions that when existing tools do not meet the needs of developers, they may create custom solutions. He introduces a gem he developed called ‘Hauser’
    • Bigg explains how ‘Hauser’ serves to define the current account based on subdomains in a Rails application, allowing scoped access to resources.
  • Testing and Maintenance:

    • Testing is crucial; Bigg stresses the importance of regression testing to ensure the application functions correctly over time, especially in multi-tenant environments.
  • Billing and Subscription Management:

    • The discussion touches on billing features, recommending the use of Stripe for handling subscription payments due to its clear documentation and ease of use compared to other solutions like PayPal.

Conclusion:

Bigg wraps up by encouraging the audience to consider these aspects when designing multi-tenant applications in Rails. He shares his book on the subject, reflecting the content of his talk to help developers streamline their understanding and implementation of multi-tenancy. The emphasis is on building applications that are straightforward, well-tested, and maintainable, which allows for scalability in multi-tenant contexts.

00:00:18.880 All right, I'm going to try something that I've never tried before. Apple's released iOS 8 recently, and they've got a keynote app on there. So I'm going to try and control my slides using this app, which I've never used before. Technically speaking, it's a live demo.
00:00:34.079 I work for a company called LIFX. We produce bulbs like this, and you can control these bulbs from one of these devices. Let's change it to white. There you go.
00:00:49.600 It comes in a variety of colors, so you can have green, orange, red, purple, whatever you want. You can buy them for yourself if you want at lifx.com. That's the company I work for, but I'm not going to be talking about that today. What I'm going to be talking about is multi-tenancy.
00:01:22.960 I work for LIFX, so I'm kind of obliged to speak about them at least to give them a passing mention. I just need to switch back to Keynote. I think, oh, how do you work this thing? Not to brag or anything, but I am number 21 among Rails contributors.
00:01:46.159 I make a lot of commits that are just one-word changes in documentation. That's how I got to be there. So I'm not going to talk about that either. Today, I’m talking about multi-tenancy with Rails.
00:02:11.920 I'm really excited about this because I get 1 hour and 10 minutes to speak to you since there are no lightning talks today. I’m going to try to talk really slowly because I originally planned to only speak for 40 minutes. Being able to talk for an hour and 10 minutes makes me really excited. This is my excited face from last night, and this is my excited face now.
00:02:41.599 So can anyone tell me what multi-tenancy is? Yes, exactly! It’s about running multiple customers on the same app but making them look like they're separate apps. An example of this could be Facebook; however, it’s not really the best example. Tumblr is closer to what I mean, as you have your own blog posts on Tumblr.
00:03:13.760 A better example would be GitHub. On GitHub, it’s all the same app and is collaborative, but you have organizations such as Spree. Each organization has its own set of teams, and those teams have access to specific repositories. This is how you can have separation on GitHub.
00:03:36.640 You have users, and from that user, you can get a list of repositories. Those repositories have collaborators, so you have a multi-tenant setup on GitHub, which is very handy. Another great feature of GitHub is its plans.
00:03:51.840 I love being able to sign up for GitHub and have a free trial, or if I want private repositories, I can choose to pay them seven dollars a month for their service. It's a great setup.
00:04:02.880 Now, I'm going to talk about a new social network that I think you all should join. It's something I’ve been thinking about for quite a long time. I pitched it to some people, and they've been pretty much on board; they want to start investing. But I just don’t have the time to start working on this thing because I’m writing books, I have a job, and I have a wife.
00:04:22.320 So I think we need to get a team together to start working on this next big idea: a site called Dog Life. It’s like Facebook but for dogs!
00:04:46.639 So, we’re going to build Dog Life, and this is how you’re going to build it: multi-tenancy with Rails. You know what multi-tenancy is, but you might not know what multi-tenancy with Rails is. What are you going to do about it? Well, you're going to go to Google, just like Schneems does.
00:05:04.320 Then, you're going to find this Ruby toolbox stuff and go to the multi-tenancy category. You'll find a list of gems there, which include Apartment, Milia, Access Tenant, Multi-Tenant Access, Restricted Sub-Domain, Active Record Connections, White Label Multi-Tenant PG, and Multi-Tenant MySQL. My criteria for accepting one of these gems has three pieces: is it well documented? Is it open source? How does it vary?
00:05:36.000 Sometimes there’s really great documentation, and sometimes there’s none. Even in big open source projects like Rails, when they rewrote the router for Rails 3, they didn't document it at all. So, I documented it. Then they didn't document the asset pipeline, so I documented that as well. I will not document your libraries. I’m sorry.
00:06:24.880 Access Tenant was one of the gems on that list that I recognized, and it ticks the box for being well documented. It has good, clear usage instructions. It's well maintained, has around 140 commits—not too high, a good group of contributors, and a low issue count. It also has CI on Travis, and that is currently passing.
00:06:42.480 However, its code has some leaking abstractions, and it needs some help. It has this 'current tenant' method defined in it, and I wonder what it is doing because I see it called everywhere: in the models and in the controllers.
00:07:00.800 Next, I found the Request Store gem. It turns out that it's using thread.current. Now, thread.current acts like a global variable. If you've ever done PHP, you know what global variables are. And I think we stopped using global variables when we stopped using PHP. Therefore, I believe we shouldn’t use thread.current when using Ruby, because it creates a leaky abstraction.
00:07:26.240 The next gem on that list is Apartment. Apartment ticks all those boxes: it’s well documented, well maintained, and has very clear, clean code. In that documentation, it mentions a concept called Postgres schemas. Does anyone know what those are? One person, two people, three people? Okay, great.
00:07:52.640 You’re going to use schemas to build Dog Life. If you don’t know what schemas are, you’ll need to look them up. They say a database can contain one or more named schemas, which in turn contain tables. There are several reasons why we might want to use schemas: to allow many users to use one database without interfering with each other.
00:08:20.480 This sounds much like multi-tenancy: to allow many users to use one database without interfering with each other. That essentially is multi-tenancy.
00:08:42.240 I got really excited about this because this is the thing that is going to revolutionize the concept of multi-tenancy: Postgres schemas! How come no one has figured this out before? Schemas work by using a search path. The default search path in Postgres is your currently logged-in user and then the public schema.
00:09:12.240 The way it works is this: if you've got a table in the public schema called 'things' and you try to find all the things, and that table doesn't exist in the user schema, it will bypass the user schema and then go to the public schema to find it.
00:09:46.400 But if the table exists inside of the user schema, it will find the table in the user schema, get the records from that, and completely ignore the public schema. This way, you can have your tenants existing in schemas instead of all grouped together in the public schema. That means one schema per tenant.
00:10:16.240 So you have this separation: Garcon and Fluffy are now separated in their own schemas, completely distinct. That’s really great. Apartment comes with some really fantastic helpers for this.
00:10:40.640 You can call 'Apartment.tenant.create', and it will create a schema with all the tables in it. After that, you want to get this rolling in your application so you can start expanding it. The first thing you want to do is create a scoped controller that all the accounts controllers inherit from.
00:11:01.760 We’ve got the script controller, and we want to switch when we're going to a subdomain to switch to the current tenant. So when we visit Garcon’s page, we should only see Garcon's posts, and if we go to Fluffy's page, we should only see Fluffy's posts.
00:11:30.640 Next, we want a method to define the current account. That way, we can easily find out whose posts we’re getting. I’m going to stop here for a second because I’ve got something that should keep you awake.
00:11:59.920 So, I’m going to turn this off for one second. There are five light bulbs on stage. Four in those boxes and one up there. From this point forward, there are three mistakes in my presentation. In just the slides, there are about 50 more slides.
00:12:16.160 If you find a mistake in a slide, put your hand up, turn on your microphone, and tell me what the mistake is. If you guess correctly, you’ll get a bulb! If you guess incorrectly, you’ll receive a t-shirt. It’s a win-win!
00:12:39.280 The next thing we want to do is to create an account with a schema. Because, you don’t want to call 'account.create' and then call 'apartment.tenant.create' with a schema name. So, you combine it into this model method. Then there’s nothing left to do; you just keep building your site, and it works.
00:13:01.440 Then, you end up deploying it. You’re like, okay, I’m going to deploy this thing to Heroku because we all love Heroku, right? So, you're like, how do I deploy this to Heroku? I’ve got schemas, but do schemas have any mention in Heroku documentation? Yes, it does! It has this yellow warning box on its Postgres page and says that the most common use case for using multiple schemas in a database is building a software as a service application.
00:13:32.800 Like Dog Life, where each customer has their own schema. While this technique seems compelling, we strongly recommend against it, as it has caused numerous operational problems. For instance, a moderate number of schemas, greater than 50, can severely impact the performance of Heroku's database snapshot tools, like pg backups. What this means is your daily backups are going to take longer than a day to complete, and that’s bad for business.
00:14:15.040 So, Postgres is great! That’s the TL;DR for the first half of my talk. Postgres is really great, but if you're trying to use PostgreSQL for something it’s not built for, you’re going to have a bad time.
00:14:31.760 So we can't use schemas. We’re going to give up on that whole idea. We’re going to throw out the Apartment gem and probably just not use it at all. So, what are we going to do instead? Okay, we’ll start from scratch.
00:15:15.440 So, we’re going to have an account post controller, and we’re just going to find all the posts in the database. We don’t have any schemas anymore, so we’re going to find all the posts. This is a problem because all the dog posts are going to be showing at the same time, and we don’t want that.
00:15:54.720 In order to write this feature, what are we going to do first? We’re going to test all the time! Right? Everyone’s testing, we’re on board with that. So I’ve got a question. Are you testing right now? Anyone typing? No? You’re not testing? Why not? That makes me so sad!
00:16:35.840 So, you've got to test all the time; otherwise, your codebase is going to end up like this. We’re going to write this feature, and it’s going to be fairly simple: we’re going to navigate to Fluffy's subdomain, and the current account is going to be Fluffy's account.
00:17:07.600 When we go to see Fluffy’s posts, we should only see Fluffy’s posts. So, when we run our test, it’s going to fail. We’re only expecting to see account A's posts, for instance, Fluffy's posts, but we see Garcon's and Brutus's posts as well.
00:17:38.880 The reason that is happening is that our controller fetches all the posts from the database. But really, it should be scoping them to just the right tenant. Previously, we used Apartment for that, but since we’re not using it now, I looked around for alternative solutions.
00:18:27.760 Finally, I invoked the 'not invented here' syndrome and wrote it myself. This gem is called Houser, and all you do is insert a piece of middleware. You tell it your account, and it will find the subdomain automatically and return this Houser object.
00:18:45.120 This Houser object is the account that matches the current subdomain. Then you can implement it just like you did with Apartment. You have a scoped controller that defines the current account method.
00:19:10.160 You redefine index to say current account posts, and then your test passes! That's pretty great, and you're very happy—as you should be, looking really cool!
00:19:41.360 Now we’re doing a bit more of an advanced feature where we’re going to go to Fluffy's albums and navigate to one particular album. For Fluffy, we should only see the pictures from that album, and that album should only belong to Fluffy.
00:20:39.440 The first thing we do is ensure that when we're a guest on account A's subdomain, we should only see account A’s albums, but if you really think about it, we see everyone’s albums. And why? Because we’re telling our controller to fetch all the albums across the entire database.
00:21:21.360 So, we switched that to say account scopes. In the controller, we define index to show current account albums and that works when we check.
00:22:18.640 The next step is when we're a guest on account A’s subdomain, viewing an album; we want to show only the pictures from that album. Again, we expect not to see account-based pictures, but we do. So, we have the pictures controller, and it’s the same deal as before.
00:22:39.920 Now we’re inheriting from the account scoped controller, defining album. We’ve got the album instance variable there, getting the pictures out of it. We define the album instance variable in findAlbum.
00:23:16.160 Yes! You’re right! I’m missing a before filter. Congratulations! So, even though we made mistakes in this slide, the before filter is in my code, which I’ve tested, and that test is passing. So, everyone is happy! You go to fluffy.life.com.
00:23:36.560 You go to Fluffy's albums, you go to Fluffy's birthday pictures, and you can see them. But the code is obviously wrong, and so you’ve added the before filter. The code is wrong because when navigating to Garcon's page, if you enter the right album ID, you can actually see Fluffy’s albums instead.
00:24:08.680 So, my second lesson is to regression test all the time! If you ever encounter a bug in your application, I cannot overstate the value of regression testing. There have been instances where I've had code that wasn't regression tested, and then a client came to me saying the code was broken. I thought I had deployed a fix, but weeks later they told me it's still broken.
00:24:35.919 So, regression test! That way, if it gets unfixed, you can find out when it broke. We write a regression test, we fix our code, and we are now scoping correctly using current account albums.
00:24:51.039 Now we can ship it! We’re done. We’ve got posts and albums and now we want to work on subscriptions, which I promised to return to from the beginning of this talk.
00:25:18.560 When GitHub has plans, who's ever written their own subscriptions code? One, two... that's it? Okay. Good! We’ve got some really smart people here!
00:25:43.360 So, our next feature is that we want to go to the sign-up page. When someone signs up for an account, we need to ensure something happens so that the user goes to the sign-up page. As the creators of Dog Life, we want to earn money.
00:26:39.360 Does anyone know who said, 'Money makes the world go round?' Right, exactly! Nobody ever! Personally, I’ve tried writing subscriptions code before, and it ended up badly. Users often cancel in the middle of the month, or there’s a trial period, or their card doesn’t work, or there are insufficient funds. It turns into a major headache.
00:27:48.240 So, I prefer to outsource that to my great friends at PayPal. They’re really awesome, except when they're not—which happens often. If you look up PayPal recurring billing, you see a convoluted page.
00:28:09.440 There's a tab for developers which leads to a complex blog of documentation. Every time I go to PayPal's documentation, I end up getting frustrated because they often point to documents that no longer exist.
00:28:51.200 I’ve tried implementing Express Checkout before, and it told me to open a PDF to learn about it. When I opened this document, it had a link stating that this document has been moved. So, I clicked the link only to find another document referenced that has also moved.
00:29:01.440 Ultimately, it led me to an extensive PDF filled with technical jargon about Express Checkout. So, I had decided that PayPal just isn’t for me.
00:29:22.560 Stripe, on the other hand, has a far simpler API for subscription and recurring billing. When you look up Stripe for recurring billing, you see a clear page stating, 'Stripe makes recurring and subscription-based billing easy.' Immediately, that sold me!
00:29:40.560 Their documentation is clear and concise, showing you how to create a plan, subscribe a customer to the plan. The great part is that all the code you need fits on one slide!
00:29:59.760 So, I've jumped back to PayPal; they now have a Ruby API, and it looks somewhat complicated compared to Stripe's clean approach. You create an API instance, then use a lengthy method to establish recurring payments.
00:30:25.760 Unfortunately, I've tried using PayPal’s API, and I find it incredibly frustrating. They should provide a simple API so that developers want to utilize their service.
00:30:48.560 So, for our feature, when a user signs up, we will use Stripe. And my friends, that brings me to five o'clock, which is when I was supposed to end, and I'm still going to wrap up.
00:31:14.160 If you liked this talk, please email me! If you hated this talk, please email me! If you have any questions, feel free to email me as well.
00:31:43.760 One more thing: I have a book which covers this talk in book form. It's called 'Multi-tenancy with Rails' and you can get it for a special discounted rate of ten dollars using the discount code 'ten dollars off'. Visit leanpub.com/multi-tenancy-rails, and that’s my talk. Thank you!