 Video equipment rental costs paid for by peep code screencasts. I'm Francis Alvin, CTO and co-founder of a company here in Austin, Spiceworks, and today I was going to just talk about some of the lessons we've learned and some of the tips we learned using Ruby on Rails in the last two years. The agenda was going to be, I'm just going to kind of show you our product, you'll get it right away, I'm not trying to sell you our product, we don't actually charge our product, so no reason to sell it to you. I was going to then cover some things that we think are pretty interesting that you may or may not find interesting, but since I think it's interesting and I'm talking, that's what I'm talking about. Updating remote installations, we have a whole bunch of installations out in the field and we have logic to keep them up to date with the current stuff that we're deploying. I'm going to briefly talk about performance of Ruby on the Windows operating system. I'm going to walk you through a very trivial example of how we avoid deadlock in some situations. I'm going to walk you through an extension we wrote that lets us use ActiveRecord to do work to keep track of things and then I'm going to walk you through finally a tweak that we've gone through on our network scanner that actually make it faster. Just a quick show of hands, how many people here from Austin? I can't tell you how amazing that is to me because three years ago when we started to splice work, I didn't even hardly know what Ruby was. Seeing that many people here from Austin, of course it's a local conference, but when I signed up to talk here, I was thinking maybe 50 or 100 people. This is quite amazing to me. I'm going to just quickly go over what SpiceWorks does. We build software for small, medium business IT people to keep track of their inventory, soft tickets and things like that. Like I said, we were founded in January 2006. Our mission when we found was to simplify IT management for small, medium business. At the time when we launched, we were the first free ad supported IT application. Now ad supported is becoming more and more. Today, 450,000 IT pro shops or IT pros are using SpiceWorks in 196 countries. Our app isn't even internationalized. We have under management 20 million devices by those installations and 13 million users by those. So those IT managers are managing 13 million users. And our tagline, we're trying to build the app for everything IT. This is a quick, just a timeline of a day of an IT guy. We would love it if they used us every day because guess what, we're ad supported. We don't get paid unless you use the app. So our initial product, we built something that invigorated your network and took inventory of everything you have, so your computers, your printers, your software, your hardware, yada, yada, yada. And then we built in a community into our application that lets you get advice and expertise from people in the SpiceWorks community and actually built a help desk in the SpiceWorks so you could solve problems that users have electronically and interact with them electronically. And then you could then turn around and share best practices with other SpiceWorks users in the field and hopefully eventually buy products and services with SpiceWorks. Test 22. Okay. So quickly go over our architecture. So this is kind of interesting. We have a cloud, a web form, a bunch of Linux servers running Ruby on Rails. That's our central server. Our SpiceWorks application is actually installed on a Windows computer inside the small, medium business environment. It was actually not designed that way. It was designed to be hosted, or app was designed to be hosted like Salesforce and those things, but it turned out when we actually started deploying people and talking to them. They didn't want their private inventory data leaving their network. So we end up deploying on the local machine using Ruby on Rails in that framework as well. And the UI and everything is driven from a web server launch from that Windows computer right there on site. It's kind of interesting, though, we kind of backed into this when we were originally going to host all those. We have 450,000 plus installations of this sitting there. If we had to host all those servers, it would be a nightmare. We don't have the money to do that. So this hybrid model where you actually install something locally on the site actually for us in scaling is huge. We started with just three developers, county me and our alpha version we had out in three months from when we started. I just wanted to quickly go over why we chose Ruby on Rails. We had a Java background.net, XML spring, name your favorite bingo framework. We had all that expertise I had played with it before. It only takes a year or two to write something in that. It's pretty fast. But the problem is we couldn't afford 20 developers. We had to do something in three months, not a year. We had to be on time. We couldn't delay something in three months and try to do all that stuff. So we needed something that was fast. But we also needed something that could scale with us and I heard a rumor. The other day Jason in the class I took said that he told me Rails doesn't scale, so still kind of a rumor out there. In January 2006, for us it was kind of a risky technology. I looked through the documentation and it was pretty sketchy and at the time we were looking through Python 2, it had some great documentation, pretty well-founded stuff. And I was pretty set on using Python to do what we were doing. And then my other developer that was working at the time, who was working on a prototype in two days prototyped a system that in Java would have taken weeks. And he wasn't saying, look what I can do. He was saying, look how cool this Ruby on Rails thing. And I was just looking at the passion in his face and I can do anything if you let me use this tool and I thought about it for a while. I thought it was a little riskier, but I actually don't read documentation. So the Python thing, yeah. And I just saw the other day, the Django is finally out. So we could always switch later maybe. Swiceworks today, we're adding about 1200 businesses a day. So I mean I was just about scalability. We're doing just fine. I wanted to talk about now kind of some lessons to learn on handling this. So when we have half a million installations of Ruby on Rails distributed across people, small businesses across the world. And we want to put on an update, the original idea when we were thinking about it was, can't we just update everybody every time we get a change? Maybe I check in a line of code, everyone gets it. We're all good. We write tests sometimes and what could go wrong? And support would be trivial because it would always be up to date with the latest and greatest and it's nirvana. Well, sometimes when we came up with things, we got the model wrong. Or we had to migrate data from one record to another. And some people, even though we're targeting people at 250 devices, some people had 1,000 devices or 2,000 devices or. And if I'm using an app and all of a sudden it's upgraded underneath me, it's kind of confusing. So hell, and then the great Windows, you have to use easiest way to replace a DLL on Windows is to restart the application. And then if we had all our servers up in the farm in the sky and you put an update out, they get hammered of course. So that original nirvana thing, we had to back off a little bit. So next idea we came up with is, hey, how about user uses a spice works application? If we ever put an update out, if they happen to restart, we'll just sneak it in when they restart and life will be good. Sports happy, everyone's on the latest release. Unfortunately, many people rarely restart. They'll install spice works on a server in the small, medium business, and then that service runs for months. So I said support was happy a minute ago. They're actually not happy. It's a headache. And now the additional headache is someone just restarts. They don't know they're taking an update and the app completely changes out from underneath them. So we're making rapid changes in the app and they end up. They end up having that completely change underneath them. So that's no good. Next idea was how about we update when a user gives the okay. So we tell the user there's an update available. They go ahead and walk through the update process. It's kind of manual but it gives them time to get educated and plan and let them go read the community and see if other people applied it and they'll not be the first. Who's got an iPhone brick? Still issue is a user would end up with a minor issue for months at a time because people get nagged update all the time from software all over the place. So now support has to worry about multiple versions in the back when people are running the issues that we fixed months ago and they're still they're still behind because they haven't updated. And I mean the obvious current implementation is a hybrid approach. What we do is if someone, if there's a major update available that has to replace DLLs and things like that, we tell them and they go through the upgrade process. If we have a minor what I would call template change, a tweak, we can just push that out effectively between page clicks. Users clicking on a page. None of you guys ever have IE6 problems or CSS, nothing like that. When we write them, they're right the first time. Well, maybe I have half our dep teams that in here and they're gonna kill me after this. Yeah, embarrassingly, you misspell a word on a page. Well, it's stuck there for months unless you have an easy way to update it. So very, very simple approach here, our application. The Ruby container sitting on the small meeting business makes a heartbeat call up to our main servers. The update manager says, hey, here's a major version, here's a minor version. And then the update is pretty easy. Am I on the current version? No, tell them they should update. Am I on the minor version? No, just replace the gems right there on the fly. And that brings up a slight trick is how do you do that? So if you're in development mode in Rails, it's not a problem. You just change whatever you want and everything seems to work. In production mode, it's not so easy. So one of our developers figured this out. They kind of looked at the way the demo did it and wrote a custom Mongo handler that would, we broke our system up into 10, 15 gems. And then the gems and the file paths that those are on are all handled by this Mongo handler. And then as soon as we want to update it, we lock Mongo, we switch the paths and remove all the current templates. This is the simple code from Active Record that clears the templates after they get loaded in. So it's not rocket science here. So I'm going to slightly switch gears. Ruby performance. So Ruby, for performance wise, for Perl, things like Perl Ruby, it seems pretty fast. But we did notice one thing. Stock Ruby on Windows, I guess that's the one we downloaded from one click or wherever we were getting it from three years ago. Seen much slower running on Windows than it did on Mac OS X or Linux. After last night, I can see why it runs faster on Linux. But it's interesting, we had a VMware server. We had Fedora running on it. We ran the Ruby benchmarks in there. And on the same host, same host, operating system Windows, ran the benchmark from there from a command.exe window, 40% slower. That's like, so like you do a migration, it now takes twice as long. Or whatever it is, it's slower. I still can't believe, because it always seems slower walking around saying, yeah, it's slower, it's Windows, whatever. And then we finally benchmarked and said, you gotta be kidding me. How could it be that much slower? Our initial approach was, we're just going to wait for the next version. Someday we'll get this new 191, new version of Rails. Things are getting better all the time. Well, we started getting, adding a bunch of stuff to our app and speed became a slight issue. So I had one of our developers, while we were waiting, why don't you see if there's anything that can be done? And this is actually pretty interesting. So he cracks open the Ruby build files for Windows, MSVC optimization flag is off. He goes, well, that's kind of interesting. Turns it on, a bunch of things don't compile, because a bunch of things are broken. He actually tracked them down, like reg, string, pack, scan, random. There's a few places in the Ruby code that can't be optimized by the Microsoft optimizer. Once those are fixed, you can turn the optimization flag on and of course you get better performance. And this is also kind of interesting, because we don't have a profiler that runs on Windows. We haven't spent that money on that. So basically ran GPROF on a Unix type machine, looking for hotspots in a val.c. Actually read this paper down here talking about what compilers do and what things manage performance. And one of the things that was in there is if you change a function call from a C deco call to fast call, it's actually about 2% faster calling fast call. So you hit a function in a val.c, they get hit all the time. You hit fast call, you get a 2% improvement. And then many of the calls you make in a val.c might call 6, 8, 10 functions. So that 2% starts adding up across all the function calls. That's URL and this is the results. It's kind of interesting. Didn't do, it's not a whole lot of work. You guys can have the diffs if you want them. It's what you see is 25, 30, 40% faster now just by doing those couple of tweaks. Now that's interesting, we want loop time in the benchmark. We actually have a few loops in our code, so that helped out a lot. Okay, while we're still waiting, the next thing we're doing right now is looking at MSVC 9, Visual Studio 2008. I guess there's this cross-module optimization in the compiler that can look across modules and optimize things together. And then this other concept of running Ruby in our environment and profiling it to guide the optimization, that concept seems promising too. I think you end up with a tweaked version of Ruby for your environment then. I got a free tip. If you're ever running a software company and you need to get a push out on the weekend, make sure the opposition has control of the heating and air conditioning. This is a picture from us last January when we released our 2.1 release. I think it was in the 20s in Austin. Happens every once in a while. It was probably 45 degrees while we were trying to do this. You see Kevin here, he's got gloves on. I was watching him try to tie-foot those on. That's pretty funny to do a deployment with bare mitts on. Justin, he's in the audience. He's got bubble wrap around him to keep him warm because he said it worked. I think it looked funny. We now have on-site control. The release before that in the summer, it was 100 degrees on the weekend and there's no air conditioning. So it's worse. Everyone stinks when you're doing that. Next thing I wanted to talk about was a Ruby on Rails plugin that we wrote that allowed customers to tack on additional attributes to the things that their devices or their tickets and override existing attributes that are on those things. Here we have a detailed panel from a device that's got a bunch of information about the device. The last user, the owner, the name of it, its IP address, its model number, so on and so forth. We have that asset tag highlighted because the scanner, when it collected the information off this machine, couldn't find one, so it basically put in a blank asset tag. The user actually, the IT guy actually has a spreadsheet with the asset tag on it, so he'd actually like to put the real asset tag in there without fixing the scanner, so to do it right, you would just like to be able to type it in. So our Rails model, we got a column on devices called asset tag as a string, but the problem is we now have two owners, the scanner thinks it owns it and the SpiceWorks main app thinks it owns it. Of course the user's always right, so the SpiceWorks main app owns it. If he clicks at it, types at it, types in the real value and saves it, we remember that value, so yeah, not that hard, but next time the scanner runs the way it was implemented before, it would just wipe that out because it's got blank for an asset tag and that must be right from the scanner's point of view. So we wrote this acts as modifiable plugin that you could programmatically control things like this, so our device model actually has this additional thing that asset tag is an overridden column and then that allows you to do things like, well it adds a new method called base underscore, so if you say base underscore, I will show you the default one was blank. If the user set the asset tag to A, B, C, 1, 2, 3, like I showed you in the UI, just for clarity's sake, I set the base asset tag from the scanner to A tag from scanner. The asset tag from the scanner then is not displayed if you look at asset tag, it's actually the one the user typed in and if I clear out the asset tag, it moves back to the one from the scanner. Another example that's kind of very similar in the same plugin is a user has attributes that they wanna add to our model. I have Cust 1 and Cust 2, custom attribute 1, 2, but it could be something like an office or whatever they wanna track. This is also programmatically controlled and that UI right there actually runs this type of Ruby and then it ends up creating new columns in the SQL schema and then active records does the work. So there they are, they show up when he typed them in and now they're on the model and everything we do from reporting to whatever has those new custom attributes on it. I do wanna say we took a couple of attempts at this. The first attempt we used the classic B&F format where we had a separate table with overrides on it and then it had a one to many relationship, actually polymorphic because it was device or ticket that lets you have overrides in that subsequent table. The big issue with that is every time you get something from Active Record now you have to do a join through those tables to get the overridden values. So if you start looking at the SQL that's coming from Active Record, they start getting longer and longer, you start joining through other things. You end up with pages of SQL where it would have been, in our case it ends up to be much simpler just to have it right on the model. So we had it on site, I had it in the architecture there we have a SQLite database that sits in the customer environment and it has all the data collected from inventory and the tickets and all that in it. And our initial way we deployed it we have Ruby on Rails and we're going in the Swissworks main app we have it doing the reading and writing to the database. And then when the scanner wanted to send data up to the installation, it actually used HTTP to post it through the server server single threaded. So now there's no deadlock here because when the scanner writes data it's all has exclusive access and then if the user's using the web server at the same time he just waits for the scanner to finish whatever it's writing he gets in right after that. This worked fine for our first release unfortunately we started adding more and more data to the scanner. So when it was running it could just hammer the web app to a point where you couldn't get in if you were the desktop admin. Another interesting fact is 30% of the time of the scanner in this case was used to marshal the data over HTTP. So it actually slowed down the scanner, slowed down the app. And so what we really wanted was multiple database writers. One of the scanner to write directly to the database and Swissworks main app to write directly database. They're separate processes. As long as only one is writing it's actually not a problem. SQLite handles it pretty well. If both are writing you get a deadlock issue. Deadlock's not good because you get starved. We did find for SQLite, Janice Buck wrote this deadlock retry plugin and we took it down and thought we were all done. Turned out it was three years old and it didn't work for the commit side. But we ended up tweaking it so that it worked in our environment. And then we had a special consideration is when the IT admin is using the desktop we want him to have priority over the scanner. If the scanner has to stall waiting, there's no big deal. We want the guy using the app to get priority. So we tweaked it so that if the Swissworks app is trying to get access to it and the scanner's blocking it, we basically signal the scanner that the app is in distress, the app itself waits for a minute and then the scanner sees that and stops writing. Goes into a sleep. The app then gets access to the database. The user gets his data and now deadlock's avoided. That seems pretty straightforward. It's kind of a classic textbook, deadlock avoidance. The interesting thing is when we started looking everyone was saying you couldn't do it with SQLite. You can if you want. So this is probably the biggest lesson we've learned but I've seen a lot of talks. Everyone says this, I'm just gonna just say it again. It's easy to pile on. You have to know who your user is. One interesting thing that we did in our app though is so we know who our user is. These are pictures from site visits for Swissworks users. We visit them all the time. We also built in our app feedback and they can tell us exactly what they want. I can't tell you as a developer how interesting it is to have a conversation about what to build next and then say hey, what do they want? Oh, we should probably build that because if we build what they want, they'll use it. If they use it, we can run ads. Hey, that's a great idea. Instead of sitting in a product discussion and saying we want this, we want that and you know how that goes. Next thing is, we only have three full-time support people and with close to half a million users, that's not very many. So we actually build a community right in where people, they can look at FAQs and fix things and a lot of our Swissworks users spend their free time solving problems for other people. It's kind of interesting and some of them actually probably know it better than us but we also have our developers watch this and this is like a secret sauce if you will. Our developers can figure out what people are having problems with so the next time they work on something that's related to it, they can actually make it so that other people understand. I'm just gonna go over a really simple example of that. This is our monitors and alerts page. It's pretty cool. It's got AJAXI stuff all over it. These are shipped out of the box. Some users don't even know we have monitors and alerts but they're shipped out of the box and they're configured automatically for them. Just that top one there, any disk space. You know, if the disk is less than 25% free, they'll get a monitor on a device when we scan it. Pretty straightforward. Well, we have one guy in the support thing saying, well that monitor's no good. I only care about if it's 10% free. And it's kind of interesting. There's a line right there in the UI that says, to edit a monitor set and click on it. But if you're not a AJAX kind of guy and you're used to old web forms, can you honestly tell from this that you can edit it? I mean, you can read it and figure it out but can you honestly tell? You actually, in ours, you have to hover over and turn it yellow. See now that's giving you a hint that you can edit it. And if you click on it, then you get a drop. You click on it, it switched to a dropdown. So you didn't know it was a dropdown before. Pretty slick, isn't it? Then you click the dropdown. Of course, he has all the options he ever wanted to do but it's kind of interesting. You give up, for some usability things, you give up or cleanliness for UI, you give up usability. And we're actually working on this now. We're gonna make it more apparent that you can actually edit that without having to read the text on what's going on. But this is a common struggle for us. There was that thing a few weeks ago showing a guy, said he showed a picture of a five-pointed star, how many dots are on the star. And they pulled the audience and they got numbers from five to, I don't know, 15. It was something, and I was just baffled at that. But I mean, we face that every day on our app but it's interesting going back to our secret sauce, our developers are exposed to this every day because they actually get into the community and help figure out what's going on with people. I can't emphasize that enough. Know who you're building your software for, build what they want. The last thing we have is this built-in community for the users. This is kind of interesting, or they just kind of, I know, it's all the latest both social networking, but they have groups, they self-form, they start talking about stuff. When this active director group, when they started it, it now gets like 10 posts a day, you can't keep up with it. It's kind of, we didn't really, I mean, maybe when we thought about it, we thought people use it, but actually using it actively. It's kind of interesting. It's also kind of interesting if you ask one of our IT guys, if they have any extra time to do anything else, they'll say, no, they're busy the whole time and then you'll see last week that I contributed 600 messages. They might not think they're interested but they get sucked in and they start helping other people. And a fact that we don't charge for our software that helps them feel like they're actually helping the Swashworths cause. Okay, total veer down to a Ruby example. The Swashworth scanner, all it does is scan your network, looking for devices, classify what they are and then get detailed information from them, like on a Mac OS Xbox, it'll log in over SSH, it'll start running a bunch of commands like DF or system properties to get the software. On and on, on a Windows box, it'll connect to the Windows management framework and grab a bunch of management information that way. On a router, it'll connect with SNMP and collect a bunch of information that way. But all in all, it's a pretty simple algorithm. For each scan entry, a scan entry could be multiple devices, like a list of them. It normalizes them to make sure that it's only probing one device one time. And then for each one of those, it just has to classify them. And after it classifies them, it knows what they are, it can go get detailed information like I was talking about. Well, you know, that's pretty simple. It's not exactly fast though, because it's all linear. You just wait for the beginning and end. If you have a lot of, our design point was 250 devices, it actually, for our initial release, was plenty good enough. But if you look at it, those two steps, the normalize and then walking through each one to classify it, well, that was classic Ruby. Normalize to prospects should be yielding back results as it gets them. So you can change that to this. And it's actually cleaner, less code. You get to classify the devices as they become available by the code. So that actually improves performance a lot because the perception is waiting for all the entries to be scanned versus I found one, I found one, I found one. It's like it's actually doing something. The next thing you can see is that last map, once it goes and gets the detailed information, that actually can be done in parallel as well. We actually have our own class that wraps threads in a number of threads and walks around it. And this ends up, getting in parallel all the information from the devices max threads at a time. So if you gave it 16 threads, it would run that, obtain extended data, 16 times in parallel and 16 different threads. Using threads, it's well documented. Using threads in the older Ruby is pretty difficult. There's a lot of stuff that doesn't make sense. Like using existing thread versus creating new thread, when you do hundreds and thousands of them, it's better to keep your existing ones. Anyway, I know this is all getting better so I'm not gonna spend a lot of time on it. I always thought this would be cool though, just instead of we got the each operator on array, I like this, why can't I say reduce on an array and have it do the block against every element, however it wants. I mean, I made up this arbitrary max those things. Ideally maybe max threads wouldn't even be passed in, the computer itself would figure out how to do that in parallel across all those things. I think that would be really nice. Then I just got through talking about how cool that yield was, so it yields back the results in the middle. If that normalized prospects thing takes a long time, that you still get starved, the classified things still get starved, so you need to do both in parallel. So I've switched it out. Now, interesting thing is when I switch it up, this is not very common for me and Ruby. Usually when I use Ruby and you add something, it gets cleaner. In this case, I'm not quite there yet because it doesn't look cleaner, it's more complicated. The first thing I'm doing there is setting up the thing that'll do the work and then as I get things to work on, I add it to the thing that does the work and then I wait for it to finish. Anyone else later, if you see me in the hall and you've got a cleaner way to do this, I'm all ears. But this is currently how our scanner works. And that initial thing I was showing you, the normalized plus classifies phase per device was taking about 25 seconds, 250 devices. It's not too bad, might be an hour or so. Now, doing those two little things, switch it to four seconds of device. And then when you actually film, the details went from 45 to 20 seconds doing it in parallel. There's a lot of data being collected there. Subsequent scans are much faster because we cache it. The other thing is we now collect about twice as much data as we did before. Well, we just put a release out with that. Four seconds is two seconds because we wrote a custom Ruby extension in C to do some of the pinging. And that actually made it faster still. Are we gonna rewrite this for Ruby 1.9? Yeah, sure. Yeah, the fibers, the revactor stuff, that all looks really, really good. The stuff I'm also talking about, about parallelization and the version after that, I'm sure we're gonna be jumping all over that because even though it's a scanner, it's fast enough, you always want it to be as fast as possible. But I can say just looking at what they're doing and the stuff they're working on, it's the future is really promising for this kind of stuff. I have another tip. If you're into changing things in the UI, just be careful. So our users, we had this voting scheme where you vote for things you want and we have a pepper icon with a count of how many people voted for it. Well, our founder, Scott Abel, our co-founder, Scott Abel, he's really into the UI and I think he didn't like how much space that pepper was taking up. So we took it out and we used an up and a down arrow to get a vote on it, kind of like dig. Well, the users revolted after we did that and they wanted their pepper back. Yeah, here's the pepper. Just mentally, this is kind of off the top of the head. I can probably think of a hundred more if you gave me time but things not to do. We originally had this, software versioning has multiple levels, 1.3.7.2 and our IP address is not sorted numerically. So we wrote this Ruby extension that sorted all the data that was coming out. It's actually quite easy to write a SQL-wide extension to do whatever you want and we were calling it, it works, it's really slow. I'd say if you're gonna write something, it needs to be fast, do that one and see. It's, you're already in C down at the level anyway so it's not the end of the world. This is another one, it's kind of tough. We needed to search in our app and we have searched in our app, we use Ferret. We used to use SQL and do queries and looking for strings and things like that but I'm not real happy with what we have with Ferret. Now, it could be we don't have it tuned right and we're gonna keep working on it but if you came and asked me, hey, should I put Ferret on my app? I would say be very careful. And then this last one is like total no-brainer, never do a major update without beta testing. Yeah, we actually did this once. It's not a good, the support guys just not happy. And I got one more thing here on this, Rails doesn't scale. This is our first UI when we released 1.0 and whatever it was made, 2006. A few days before we released it, we had a lot of white space in it so I showed you a picture of Scott a minute ago. He had our UI guys put in this, kind of this, call it a dynamic report. It's got a bunch of dimensions of the data all sitting here. How many devices we found? How many were identified? Which ones are currently online? What types they were? Who the OS is? Are they static, dynamic? Oh, you can see all, pretty interesting. Well, we put this all in, we're about to release it and all of a sudden our little Rails app just started just take 10 seconds to load this page for just a small set of data. We had a guy with 1,045,000 records took 45 seconds to load this page. Obviously it's because Rails doesn't scale. Well, actually, not quite, it's because we don't have a program. We looked at the query analyzer, 80 queries to do this. You know, how could that possibly be 80 queries? Well, it's easy, you all found, oh, that's just device.find where type is not unknown. Unknowns, device.find, count, where type, you know. So you can see how you can easily get in that. The point is, you know, just take a look, don't do stupid things. This is actually one or two queries in real life. It loads in under a second for very large sets of data. It's just, you can blame something on the infrastructure you want. Just make sure you know what you're doing. That's pretty much all I got. I just, few parting shots. You might think, well, you didn't really learn anything from what I said. Everything I said was pretty basic. It was basic, deadlock detection, basic this, basic that. Yeah, it's right. It's pretty darn easy. That's pretty good for a software developer, right? Miles was talking about where he learned how to program Fortran. Yeah, Fortran on cards. I dropped a deck of cards once. It took me an hour to get them back in place. That was tough. You couldn't do something like this like that, but what we got, we have the tools, we have people are writing gems, people are sharing stuff faster. Yeah, I hope someone comes up and tells me, hey, you did something, three things are stupid. I'm all ears. I love this stuff. You can't get this in like, you go to Microsoft development thing. It's just a different thing. That's kind of the cool thing about Ruby, actually. The other thing, this is totally obvious too. Good is good enough. You get something out there. You go, oh, this one page doesn't load. It takes 10 seconds. How about just tweaking it and fixing it? The one thing about Ruby and the reason why we chose it in the end was I can prototype something faster than I can document something. That's in Java, no way. I can write a Word document that describes what I wanted to do way faster than I could build it if I could ever build it. So our way is you have an idea, prototype it. We like it. We'll take it. We're gonna tweak it. We tweak it. Write the document. I don't care about the document. Show me what it does. That's so empowering. If I wanted to write documents, I would have majored in English. Yeah, guess what? Ruby and Rails scale just fine. Yeah, this is just so obvious, but this is, being a programmer, I can't tell you how much fun it is. I wake up in the morning, check my program. I look at it, tweak it. People get paid to program and play with stuff like this. Be passionate, have fun. This is awesome. And that's pretty much all I got unless you got any questions. Well, if you want to send me something or you want something, just send me an email. Video equipment rental costs paid for by Peep Code Screencasts.