RailsConf 2018: Closing Keynote by Aaron Patterson

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
(upbeat music) - Hello, hello. I mean I guess, I guess if we get stuck in Minneapolis that's where we're supposed to be anyway, so. Right, I mean is that why you were late? You were scouting locations? (laughing) Okay hold on, I gotta do this real quick okay. This is actually for expenses, to prove I was here. (audience laughing) (mumbles) We're on? I'm on? Okay, hold on one sec. Hold on. No, come on. (audience laughing) One sec, one sec, cheese okay. Okay all right, okay there we go. All right. (audience laughing) Let's start, let's start. So this is a just in time, just in time presentation, not to be confused with Justin Time. I'm Aaron. (laughing) So, I am an extremely, extremely horrible procrastinator and instead of working on my presentation, I kept putting stuff and like doing actual work and putting stuff in slack and Eileen suggested that I titled my talk Things I've been Doing Besides Preparing my Talk because I get a lot done, when I am supposed to be doing something else. (laughing) Anyway, I get extremely, extremely nervous about giving presentations and I'm always paranoid that I'm going to go way, way too fast and I won't have enough slides. So the other day, like the other day DHH made this tweet he said, "I'm just going to say it I hate Foo, "Bar, and Baz as prototypical variable names "it's time for a change." And this made me laugh a lot. So, I made a little video about it and I tweeted about it I'm like, "There's one slide done yes." This is what I'm doing, working on my presentation and then, and then, and then DHH likes and I'm like, "Yes, there's two slides done." (audience laughing) I'm getting like four or five slides out of this. (laughing) So, I think the presentation is going okay so far. Anyway oh, hello there. My name is Aaron Patterson, I may look different on the internet than I do in person this is what I look like online, if you don't recognize me, some of you may have noticed maybe a cat picture coming up on your screen, I don't know if you recognize this. But Jason and Kevin I'm not sure why, you should have accepted my Airdrop, I'm not sure this one too, geez. Gabrielle come on, just trying to send you photos of my cat please. (laughing) Anyway, I work for a very small startup company called GitHub, I don't know if you've heard of it. It is the only legit company I've ever worked for. (laughing) I really enjoy using git, but I will not force push it on you. Now I make this pawn at every conference and my co-workers told me that I really need to branch out. (audience laughing) But I had to say to them, "Look, look "I'm just really committed to these git based puns "not even not even flogging me is going to stop me, "in fact even if you reflog me that wouldn't stop me." (audience laughing) Anyway, if you don't like these get based puns we could switch to SVN, but I just don't know what the git diff would be. (audience laughing) How long do we have left? (audience laughing) All right, so. (laughing) I love, I love titles and at GitHub I am a level 5 engineer. (laughing) I am level 5, and despite the fact that I have played Xenoblade for over 90 hours I still don't have enough XP to level up to level six engineering. So, I'm going to be working on this every day and my boss is here in the audience. I'm going to be working on Xenoblade every day until I can get up to a level six engineer. (laughing) So, I guess I should go to real content. No, not yet. I love, I love local businesses. I love local businesses before I visit any place I look up the best local businesses for that place so that I can go help support them. And I was inclined, to learn more about Pittsburgh. Come on. Come on (audience laughing) Come on, anyway so you may or may not know this, Heinz is a local, local business so I bought some ketchup this hashtag local yes. These are really exciting, I wanted to support local businesses even more so I picked up some Heinz Texas barbecue sauce that is also hashtag local this is exciting. Then I went and picked up some Heinz beans and these are actually amazing because there's 57 varieties of beans you may not know this, it's 57 different varieties. But anyway, I thought this is really neat but unfortunately I looked at the back of the can and they're actually made in England, so they're hashtag not local, so I apologize for that I was trying real hard I'm not sure what the deal is there. So anyway, I love local businesses I hope you got the point. Pittsburgh people, yes yes. Come on, I know there's like five people in the audience that got this one and I'm sure you're laughing real hard. (laughing) Thank You Evan. (audience laughing) The end of RailsConf where I waste one of your hours. (laughing) So the day before yesterday, I learned. I've got 99 problems I guess they're all yours. (audience laughing) Anyway, I love I really, really love coming to RailsConf and it's really exciting for me to be here. I like to visit everyone, everyone here I feel like it's kind of a homecoming for me like I get to see all the you know old faces and new faces, meet new people see other folks on the Rails core team, do a lot of mingling. So, I wanna catch up everybody on what I've been doing over the past year, so this past year I actually, I became an uncle which is exciting for me I've never been an uncle before. This is my niece, this is me touching her face. (audience laughing) Surprised my sister let me do this. (laughing) Since this past year I actually became extremely famous in Japan, this is true and if we have time at the end of the presentation I will tell you exactly why, but I made this tweet right here it is one character one character and it garnered 81,000 likes which from a social media perspective is like a really good deal, like characters per like is like very, very efficient here. This is worth my time, so any of you social media managers out there look at this one, look at your likes per character I mean this one's like great. Anyway, so this is well worth your investment. I decided to become a thought leader again, every year I wanna be a thought leader but it just doesn't work out, but the only role model I have for thought leadership is DHH so I decided the first thing that I should do is get a headshot like his, so I've got this new headshot. (audience laughing) You may notice the resolution is a little bit off on mine, but that's because I've got an older Mac it's still at a 480p camera, so I'm sorry. Not quite there, I'm still working on it but anyway I follow him on Instagram and he has this amazing desk, so I thought "Well, "I wanna have an amazing desk like this too "I'll set it up like, I can become a thought leader too "if I have the right desk." So I wanna show you like I put together mine like this, this is DHH's office, and here's mine it's exactly the same you can see, it's perfect. In fact one thing I noticed when I took this photo there there's actually that photo. (audience laughing) But really, I'm actually really, really good friends with DHH just check out this selfie that we took together. (audience laughing) In fact, in fact he invited me over to his house the other day, so I went and I took a photo and this is it. (audience laughing) Okay yeah, it's a little dark isn't it? (laughing) All right, this is just a test slide so I was doing a test slide here I just wanted to see if this would actually work. I don't know if you can see it, there's something right there in the slide it's right between the S and the W, I'm not sure if you can see that right there but it is a zero with a space, that is character that's between those two and we're actually gonna talk about that, we're gonna talk about that in a bit this is just a test slide. Anyway so, this tweet really annoys me. (audience laughing) So, I have like really weird conversations in my head like I think I have a problem focusing or something, I don't know I have a weird conversations with people in my head, I think okay we're gonna talk about this, and this is how the conversation is going to go and I kept replaying it over in my head and I'm like you know what, I have to I have to get this down somewhere so I made a video out of it, this is the video. (laughing) Anyway, so this is what's going through my head like I'm having this conversation in my head. Unfortunately like, DHH is the last one to say something so anyway, I'm thinking about this I'm thinking about this Foo, Bar and Baz is prototypical variable names and then I'm thinking about code and Ruby and I don't know if all of you know this but a valid identifier in Ruby is anything that is not a space basically. So this code works. I'm defining a method that's named smiley face and I'm calling the smiley face method there at the bottom so if I run this it'll work and print out neat. This also works like I've got a method with smiley face and it takes keyword arguments, and the keyword is a green heart. Defaults to a train and you can call it in passing keyword arguments and it prints out that little trolley thing there. So this is what I was thinking about is Foo Bar in baz because they're really just you know representing something I thought, "Okay well, what what else can we do with this code?" Here's another example. (audience laughing) Can anyone anyone see what's going on with this code? I'll wait, I'm gonna wait for you to type it out on your laptop you can give it a try, it actually works, this really works. No okay, all right. So I'll explain it right here at the very top the name of this method is actually a non-breaking space so there's a non-breaking space right there, and down at the bottom there is another non-breaking space so I'm calling the method with non-breaking space and if we look at this in vim you can actually see it. On the right is the source code in vim, you can see the non-breaking space character there but when we cut the file and actually run it, it totally works fine. So let's extend the program just a little bit we're gonna do one extension to it here. In this case we have a method, the name of the method is a non-breaking space and we have a parameter given to the method that is a zero width joiner. So right there that's our, name our parameter we're calling puts. So, here it is again in vim you can see like right there and of course this is Seattle style there's no parentheses. So, you can see in vim we've got the non-breaking space and the other space, and it totally works fine. So what I'm proposing today is that instead of using Foo and Bar, we replace Foo with zero width joiner, we replace a bar with non-breaking space. So all right, with that let's let's write some production. Production ready code. (laughing) I dare all of you to put that in your app code tonight, wait don't do that, don't do that, don't, don't. Okay, all right so today, today surprise, surprise we're going to talk about performance. I always give performance talks, we actually just had our performance reviews at work. (laughing) My boss is in the audience. (laughing) You did a great job of my performance review. (audience laughing) Thank you, anyway so we're gonna talk about performance today but I don't wanna talk about the speed of your code I wanna talk about the speed of your development like your actual development, getting features done and actually making that website that you need to ship. Now, the thing is I was thinking about this and we're always getting faster, we're learning new tools, we're learning new technologies we're learning new techniques. But unfortunately, it seems like as time goes on your team velocity gets slower and slower. It takes longer to add a new feature, it takes longer to ship your code. So why does this happen? This seems paradoxical, who is this mythical man? Why are we concerned with his month? (laughing) But seriously why are we slower? Why are we slower to ship these features over time, and what can we do about it? Well, there's different ways that we can deal with some sort of situation like this, one of those ways to deal with this situation is to rewrite. We wanna rewrite our code, we wanna chase that dragon, that dragon that we felt that high when we wrote Rails new and we can get all of those features out the door really quickly. Wanna throw away all that code and say, "Okay, I'm gonna begin again." And feel that exhilaration when you can ship new features quickly. But if you're doing this all the time, if you're just rewriting something all you're doing is going down the same path over and over again. Maybe the weather has changed but the places you're going are exactly the same, what good is this velocity that you get if you're just running down the same path over and over again. Now, the day before yesterday I learned that if you don't wanna deal with these hard problems in your program you can just hire someone to fix it, hire someone better than you to fix that scaling issue in your application. Now unfortunately, I cannot afford that, so I have to pick a different solution I choose to refactor. We could we could choose to refactor our code, we could make performance improvements and pay off technical debt etc, etc. But the problem is, it seems like we have these two different arcs juxtaposed against each other as application developers. It seems that we either have to be software writers or we have to be a PhD in computer science to work on an application. Now in my opinion there has to be something in between we can't just only be software writers we can't only be PhDs in computer science, we have to go deeper into the application we to go down this cliff so that we can actually solve these more difficult problems. Now, as we learned day before yesterday, I am the only one who can fix this. (audience laughing) And we've recently learned that, that doesn't actually work out. The truth is that you're the only one that can fix your application code. So the Generic features that Rails provides gives you a great bootstrap you can get going early, we give you all the things that a generic web application would require to ship a feature but as time goes on your application is getting more and more unique. You know your business logic, you know your business I don't know your business, I can't help you with that. So I think this is one of the reasons that application development starts to slow down over time as we have to get better at our particular domain. So those generic features and Rails give us a huge, huge boost and I think this is why I like to focus on Rails a lot, this is why I speak about profiling and speed improvements with Rails is because, I want people to be able to take their Rails application and live with it for a long time, I want them to have a successful business not have to run into these scaling issues as often. So, I love Rails and I want Rails to continue for a very long time, so this is why I study these particular things. So today we're gonna be talking about profiling and tuning we're gonna specifically look at Runtime and Memory management, which sometimes I like to call time and space because it makes me feel like I'm on Star Trek. (laughing) It makes me feel a little bit more important than I actually am. So, let's look at some stuff we're gonna look at types of profilers and how to use them, how to build them and basically how to improve our application performance with them. So there are two types of profilers that I typically deal with, one is called an exact profiler and one is called a sampling profiler. And to figure out which profiler you need when you're profiling your application, you just need to ask yourself a couple questions. The thing that you're trying to solve, if you're at thing is how many? How many of something? How many method calls? How many object allocations? If you need to know an exact precise number then you wanna use an exact profiler. Now if you don't care about an exact number like let's say you need to know, what is the slowest method? What percentage of time do this particular thing take? You wanna use a sampling profiler. So, if your question is how much? Use a sampling profiler. So in order to figure out which one you need, just ask the question out loud. What is the thing that I'm trying to do? What am I trying to solve? So let's take a look at exact profilers, we're gonna actually implement an exact profiler so that we can understand the way that it works. This is an example of an exact profiler written in Ruby it uses the trace point API. This trace point basically what it does, is it hooks into the call method and every time a method gets called once we enable the trace point, our hook method will actually get called. So we listen for the event and every time we get an event we just keep track of that an increment encounter. And you'll see at the bottom of this code here it outputs the result which says okay benchmark me was called once, fast was called a thousand times and slow was called 10 times. Now if this code wasn't so simple you might look at this and think, "Oh, the place I need to optimize "is the fast method, or the method called fast. "That's the thing I need to optimize is called the most." But obviously when we look at this code we can tell the slow method is a slow one, so this is a particular case where we wouldn't want to use an exact profiler, this is misleading us. If our goal is to speed up this code we don't wanna use an exact profiler in this particular example. Now the other profiler is a sampling profiler and this sampling profiler what it does, is it stops your program at intervals, regular intervals and it asks the program, "What are you thinking about?" (audience laughing) So every so often, it asks what you're thinking about it just records it. So every so often, we have a program here that's running so time is along the bottom our program runs. It stops we record what it's doing and we keep doing this at regular intervals until we're done sampling it. Now the idea behind this type of profiler is that let's say you have a slow method, if your method is slow then the probability of being in that method any time you stop the program is higher. So we can tell that if we're in this one particular method frequently, if that shows up frequently within these samples that's probably where our slow code is. So we can write a sampling profiler and Ruby here's an example of it. What this profiler does is it starts up a thread and it asks the main thread at an interval, what are you doing? What are you doing? What are you doing? Every so often, and you can see at the bottom we have our results from that and in this particular example we have almost 6,500 samples inside the sleep method and maybe one sample inside the slow method. So we don't know how many times the sleep method was called but we do know that when it's being sampled it's very frequently in the sleep method, so 99% of the time we sample we know it's inside the sleep method, so that's the thing that we need to target. Our fast method didn't even show up in this profiler. So when do you use exact versus sampling? It depends on what you're trying to measure, again if you're trying to get absolute counts using exact profiler, for example the number of objects in the system or the number of times a method was called or API or whatever. Now, if you're trying to reduce time or space then you wanna look at a sampling profiler. If you're not sure, use a sampling profiler. Sampling profiler is what you need about 99.963% of the time that is with one sample. (laughing) Check my math, it's wrong. (laughing) So let's take a look at some Runtime Profiling and Tuning, we're gonna talk about tuning your runtime to profiling your runtime and tuning your runtime. So my favorite tool for doing runtime profiling is a tool called stackprof, this thing uses MRIs built-in sampling capabilities so it doesn't spin up its own thread. You can actually use a timer inside of Ruby, Ruby has a timer inside the C code not inside the Ruby code, that lets you sample every so often. So stackprof just uses that built-in capabilities and it looks very similar, the API looks very similar to the one that we wrote earlier. We just wrap up our code inside this block execute it, and when you execute it it saves a profile to a dump file so I've specified profile.dump here and when we're done with that, we can just run stackprof on that and it dumps out all the sampling information. And this looks similar to the information we got in our profile, are just much nicer right so we get kind of the same information, 999 samples. Again, we can't tell how many times the slow method was actually called, we just know that it's in the slow method very frequently. So, we can use this to benchmark Rails boot time so let's benchmark the Rails boot process let's talk about the Rails boot process. So let's take a look at how. a breakdown of the Rails boot process. If we look at it like this, where time is moving along to the to the right there we can divide it into a few different sections so at the very beginning here, that's where we hit enter, we hit enter right there. Then we start requiring files and once we've loaded and required all the files we start what's called compilation we're compiling the app. It's not really a compilation phase essentially what it's doing, it's taking all of your rack middleware and putting it all together and then handing that middleware off to whatever your web server is, in this case we're using unicorn. So after compilation occurs we hand off the application to unicorn. Now after that when you get your first request it has to compile the views and then actually execute your business logic. So, if we look at this from a little bit higher level we can say that these steps here these requiring files in app compilation, we can say that that's our time where we're not ready for request yet. We're spending time here and we're just not ready to respond to a request, and now the first request that comes in it's gonna spend time compiling reviews and then executing the business logic for that page. And then the second request is only going to execute the business logic. So if we wanna profile these things now we know what to talk about, where we're actually looking. So knowing this, we can figure out what we need to benchmark depending on the task. So, what we're going to do now is we're gonna look at benchmarking a script, just a startup script. This is an example here we have, we just load config environment this is how you in benchmark that very first slice, before we're actually compiling views. This is going to load all of your applications information all of your I don't know models and stuff if you're using production mode maybe it's lazily loading in development, but this is how you can benchmark that overhead for your scripts. Now, if we look at the output from this you'll see okay it looks very similar to our previous output but I wanna call out one thing in here you can see that we're spending some time in garbage collection right there GC. So we can see what percentage of our time is spent in GC, and we're gonna look at, how to improve that time a little bit later. So, run time tuning, I'm gonna talk a little bit about tuning your Runtime, now the thing is Ruby will try to execute your code as fast as it can so there's not really much Runtime tuning you can do, the best thing that you can do is to write faster code. (laughing) Obviously, we want the Rails framework to be as fast as possible, but at some point we can't make the framework any faster so you need to look at your own code to see what's slow about it and the most advice I can give about that is say like, okay use these tools, find where your bottleneck is look at it and say well, is there a faster way to do the same thing? Can I do this thing in a faster way? Do we need to even do this thing so much? Maybe I only need to do it once or twice, or maybe I can defer this until a later moment, maybe I don't need to do it right now. So these are questions that you should be asking yourself as you're trying to tune your application. All right, let's take a look at memory tuning. We talked about tuning Runtime let's take a look at space, actually using memory in your application. But before we get to memory tuning we need to talk a little bit about memory, how memory is handled in Ruby and I hate this so much, this made me so mad. How did David know that I am going to talk about Garbage Collection in this presentation? Am I that transparent? Why? (laughing) He said that in his talk, and I'm like, "No, how do you know?" Anyway, so let's talk about our Garbage Collector. The GC has two responsibilities in your application, one it frees memory and this is the thing that most people think about, the garbage collector doing it's freeing up your memory. But the other thing that it does for you is it actually allocates memory. I think people don't think about this as much, and I think the reason is because of the name it's called garbage collector, and you think, "Oh, I'm just taking stuff away." But it's actually in charge of allocating your memory too. So, what is MRI's GC? I'm gonna tell you what it is, we're not gonna go through all the algorithms and stuff but I just wanna give you a high-level overview so that when you have time later you can Google these words. Because that's really what's most important is knowing the keywords to Google, seriously. So, Ruby's garbage collector is a mark and sweep garbage collector, it's generational, has incremental marking lazy sweeping and uses a free list for allocation. I feel really, really bad for the translator. (laughing) I'm sorry. (laughing) Anyway, so the generational part of this just is a optimization on top of Mark and Sweep it makes your GC faster. Incremental Marking and Lazy Sweeping mean that we have shorter pause times, so when MRIs Garbage Collector is concurrent it is not parallel, and what that means is as your program is running every once in a while has to stop and do something. Now, these incremental marketing and lazy sweeping mean that those pause times are very short and what that does is it increases the throughput of your program, so you can actually run stuff through it faster. So, one question I think is interesting is when can the GC collect garbage? Or when does the GC collect garbage? When does that happen? Well, there's three three main places if you just call GC.start it will collect garbage though it's not guaranteed, and none of these are guaranteed these are just locations where it could collect garbage, so I said can. So, if you call GC.start it will do a collection, maybe it's not guaranteed but it will don't worry. Now, I know there are case when you allocate an object, if you need to allocate an object the GC may do a collection. Or if you allocate memory via malloc, the GC may do a collection. So, allocating objects and allocating via malloc are two very different things and we're gonna talk about the differences between those in a minute. So, this leads us to what is an object in Ruby? In Ruby, an object is a 40 byte chunk so it's a chunk of memory that's 40 bytes wide. So, let's say we have some code that looks like this here at the bottom, it's a string that says, "OMG this is a very long string." Okay, now this string is obviously too large to fit inside of a 40 byte chunk, so how do we actually store this? How can we have a string that's this long? The way that it actually works is, rather than storing that string inside of the Ruby object itself, we have a string object that points at something else. So, we have a string object, a Ruby string object that points at a C string object. Now, the garbage collector is in charge of allocating these Ruby objects and malloc the system malloc, is what's in charge of allocating these strings. So, we have a Ruby object that points at a C object, so in this particular case we have two places where our GC could have happened. Now, Ruby doesn't allocate one object at a time instead it allocates a page. So an object is a 40 byte chunk and when you say give me an object it pulls that object out of a page that contains many objects. So the GC won't actually allocate one, it'll allocate a page and then it'll give you one of the objects inside of that page. So, in this, particular example we allocate our Ruby string our string is just a ruby object inside the page and it points at a different string that was allocated via malloc, and in this case we have two places where the GC may have run. So, a Slot is a location where a Ruby object may or may not be and this Slot term is important when it comes to tuning the Garbage Collector because we allocate in terms of slots, well in terms of pages and those pages have slots and this is the way that we tune it, is by the number of slots that we have available. so we can control the total amount of free space in the program, in the way that we control it is via this term slots. So let's look at some tuning parameters. GC tuning is done via environment parameters and we're gonna take a look at some of them and what they do. So this is the first one, first one we'll look at is called RUBY_GC_HEAP_INIT_SLOTS I don't expect anyone to remember this, I promise I will put these slides online so you can just google it later. But we can see how it impacts the process here if we run Ruby, you can see the number of available slots with no tuning is about 1800, we have about 1800 slots available. Now if we say INIT with 600,000 slots and we run it it has about 600,000 slots available. So, with this particular parameter we can say I want this many objects, or this many free spaces declared when the script boots, or when the program boots. So, let's take a look at that profile that we looked at a little bit earlier. This is an example here we have our app boot, this is the default case that we saw previously, no changes you can see how I ran the script at the bottom of the slide there. Now, it's spent about 16% of its time in allocation 16.5% of its time in GC. Now if we tuned the slots and say we want to init with a bunch more slots, we can do this we just say let's set that environment variable to 600,000, you can see the command at the bottom and in this case we only spent about 8.3% of our time or 8.5% of our time in the GC. So, we can see kind of an impact on that when we go to boot our application. In the top one there's no tuning and it took about 4.1 seconds and in the bottom example it took about 3.5 seconds, and this is a default application, default Rails application where I just randomly chose a number. And the nice thing about this is that we didn't have to change any of our application code and we were able to decrease the boot time just by saying hey I want you to prepare a bunch of room for allocation. So, we didn't need to change our app code at all. You can use this, if you measure the objects that are allocated in your system you can come up with a good number for this and then decrease your boot time just by tuning this particular parameter. The other thing that we can do is we can control growth and these are very long, very long. So, we can control the growth of our heap via these particular parameters, and again don't need to remember these the important is that we're applying a ratio to the amount of free space that we have in our application. The GC tries to maintain a certain amount of free space for you to allocate into, and we can specify those ratios so we have a minimum ratio, a maximum ratio and a goal ratio. So we have our mean and our max and we're trying to hit the goal so kind of a window there. Now, let's take a look at a test program we have a program here, this is our test program and it just allocates a whole bunch of objects it allocates 600,000 objects. Now, what I wanna do is, I wanna study how many times the Garbage Collector had to expand its heap, while we're running this program. So each time we allocate a new object we may have to expand that heap and we wanna look at how many times we actually had to expand the heap. So, if we set our goal to particular values we can study how many times I had to expand the heap by checking the GC count. So, if we tweak the goal numbers a little bit and then graph it, we can see a different behavior and this is an example of that behavior. Our goal, the blue line our goal is set to .2 and our green line the goal is set to .7 now the interesting thing about this is, we're able to hit that 600,000 mark in fewer GCs with a larger goal. We're essentially telling the GC, "Okay I want you to be aggressive about allocating memory "then allocate it, and then expand into that." Since we were aggressive about allocating memory we're able to hit that goal, or hit that 600,000 mark sooner So unfortunately this means that we have more memory growth, we're not being conservative about our memory growth. The blue line we're more conservative about memory growth but we have to spend more time in GCs or using more CPU time. So it really depends on what your application is doing and what you're trying to tune for. If you wanna tune for fewer GCS, you may want to increase your goal. If you want to tune for lower memory usage you may want to decrease your goal, and you can actually try this at home again this slide is literally just for being online later so don't like write this down or anything please, like you wrote down the example code with a non-breaking space in it. (laughing) All right, so let's take a look at some memory profiling I saved this for last because I actually have a story to go along with it. At work, somebody pinged me at work and we have an application at work it's called the GitHub enterprise, and basically what it is it's a version of GitHub that we ship out to clients and they host it in their data centers. It's just I guess stored in a container or something I don't really know all the details. But, one of the people on the team came to me and said, "Aaron I've noticed that "as time goes on the application that we're shipping "is consuming more and more memory, "it's getting larger and larger. "Can you help with this? "Do you know why this is?" So I said okay well I'll take a look at it and see if I can figure out what the problem is and the first tool that I reached for was stackprof because is my favorite tool to use. And this is an example of using stackprof with object allocations, we can use a sampling profiler with object allocations. So, I ran this code I required our environment and took a look at the output from that and this is what the output looked like it told me that we were spending, this isn't actually our application this is a sample app. But it said oh we're making a lot of allocations inside of the require method. Now, the problem with this type of profiling is that even though we made a lot of allocations in the require method, those objects aren't necessarily retained, they may not live for the life of the program. So, if they don't live for the life of the program we don't really care, it's not contributing to our bottom line so to speak. So in this case, we actually needed to switch to exact profiling. This is a case where we said I need to know how big is the heap? I need to count exactly the number of objects that are in memory right now. So this is a case where you want to use exact profiling. And to do that I use object space this is my favorite tool for doing exact memory profiling and the way that I use this is via Heap Dumps, so there's a way to get a heap dump and I will describe it a bit here. This dumps your heap to a JSON file and in this case I'm just dumping it to a file named my dump JSON. This line up here, the second line what it does is it enables allocation location recording that's fun to say. Any objects that are allocated before this line we don't know where they've been allocated the Garbage Collector does not keep track of where they are allocated. After this line when we call this method it tells the GC, "Hey every time an object "is allocated I want you to record where it got allocated." So anything that's allocated after that line we can find out where it was allocated and though that information will actually be inside the heap dump. So, after I enabled that I said okay let's require the application booted up after we required the allocation I say okay I wanna clear out they do it, run a GC I want you to clear out any objects that are temporary that aren't going to live for the life of the program, then dump out the heap. So, once the heap is dumped out we just have this JSON file, and each line and the JSON file represents one object, one line is one object. I know this is hard to read, so I've made it a little bit nicer here. this is an example of an array we can tell it has the type that says array it gives you a list of all the references so those are all the objects that it's pointing to. We can actually see the allocation location of that object and finally my favorite is the memory size of that object. And these JSON files make for a very extremely easy analysis. So for example, let's say I wanna know how many objects are alive? Just how many objects do I have in the system? Very easy, I just count the number of lines in the file and I know. So this example had about 185000 objects, done. Okay, so how about I wanna know what file is most popular. This is one tool I really like to use JQ if you don't have the JQ tool, get this it's amazing. Basically, it's like a really great grep for a JSON. so I can say hey, I want to extract a file just the file field, I'm gonna sort it and get unique them, then I'm gonna sort those by the number of counts and then just give the top 10. When I run this I can see okay, well the top allocations came from active support dependencies.rb so I know it came from that file. Okay, I've focused in on that file now I can use JQ again and say, "Well, I only care about allocations "that are inside that file, "so I want you to filter out all the allocations "for that file and then give me the line." then we run through the same, basically the same command again sort them, get the unique account, sort by account and then get the last 10. So we can see here okay the most popular line inside that file, is line 283. So I know okay, I need to go look at that file, look at that line and now I can kind of get an idea of why we're allocating so much stuff. So we can also answer other interesting questions like how much memory does active support allocate? So I just say okay give me all the records whose file contains active support extract the mem size from that, pipe that to a little off command and sum the total and we can see that okay active support allocated what is that? Like around three Meg's okay. So back to our performance problem at work. we ran into a thing, I wanna talk about hidden references this is the thing that we ran into. We're trying to find what has references to what, basically this heap dump contains all the information of the entire object graph we can make a graph out of it so let's talk about references. This is very simple, we have an array it references two things this is obvious right? We can see okay, we have an array it points at two symbols now here here's another example of some references in a program we have a lambda. This function foo it returns a lambda and that lambda sums the two values that you passed in right so the variable m contains two references and I promise this isn't a trick, there are no non-breaking spaces in this code. (laughing) So all right, here's a question for you here's similar code the Foo method returns a lambda how many references does this lambda have? You don't need to answer, I will tell you the answer just think, how many references of it does it have? It actually has two references. The reference is the number A and B and there are two reasons why this lambda has references to those variables one, Ruby doesn't do what's called escape analysis so it doesn't look at the Lambda block and say, "Oh, I'm not actually using those things "there's no reason to keep track of them." And the other thing which is basically an implementers nightmare is a thing called binding. (laughing) So if you asks the lambda for its binding that binding object is the environment in which the lambda was created, you can actually ask the binding, "Hey what local variables do you know about?" And it says, "Hey I know about A and B "they are local, when the lambda was created." Then you can say, "Hey binding "give me the value of A and B." So we actually have these references this lambda contains references to A and B but you can't really see it when you're just reading the code. So, I was able to find these particular locations unfortunately I didn't understand this code like I could point to it and say, "Hey, that's the problem there "we're allocating a bunch of stuff there." But I couldn't really fix it, so I worked with other folks on the team and they actually rolled out fixes for this that eliminated those hidden references and we were actually able to drop 200 gigabytes memory from our production application. So, this is like really awesome and I praise the person who did this a lot it was amazing, a really amazing change. So I wanna talk about some tools that I want and then maybe some thought leaders stuff and then we'll wrap up with another story, and maybe two stories depending on my time. So let's look at the Rails Boot Process again unfortunately so far all we've been doing is looking at looking at analysis of scripts, like maybe running DB migrate or other scripts that you run with your application. We haven't looked at anything else. What about app compilation time? I mean when we run this, it's easy to know okay this is how much our scripts are spending inside of just loading the allocate or loading the application, but what about that actual compilation time? I wanna know how long does it take to restart a web server, I wanna optimize that, how do we deal with that? So how do we deal with benchmarking the rest of these things we only looked at that one. So, today we're gonna take a look at some new rackup options that I've added in order to deal with this particular situation so rack we merged this into rack a couple weeks ago this is one of the things that I was doing instead of working on my talk. In newer versions of rack you will able to say, "Hey rack, I want you to dump the heap before "or after you come compile the application "but before you give it to the web server." Or, "I want you to profile the application "up until you give it to the web server." So we're going to build this tool into rack for you so that if you wanna know why is it taking so long for my application to restart, or be ready for requests we can actually have this built into rack and give you some information, so you know how to identify problems in your code and we've written the benchmarking code for you. So this just leaves us with two other issues, our warmup time and our run time so we're able to profile script boot time, we're able to profile compiling applications but we're missing two components. Now last year Eileen spoke about system tests and this is a really important thing so besides the fact that now you can do entire system tests this is an important refactoring on Rails itself and means that we can actually run real requests through the application. So, something that looks more like a request that you would see in production. Before we had system tests, most of the stuff this I mean we on the Rails core team know this and maybe most people did not but a lot of the integration tests were basically just BS. (laughing) And I want to say that, I'm trying to say that in the nicest way possible. They would actually run the data, run these requests through only your application it would not consider any middleware that you had in your application, and this is the advantage of the system test is that they actually run the data all the way through the middleware into your application and then back out, so you can get a more real-world idea of performance of your application. So now that we have that system in place for running these system tests. (laughing) Sorry, I said I'm a little scatterbrained right? Okay, so now that we have this system in place we can run real requests through and actually benchmark those, so we have a script for doing this at work it's called this as bench app we use this for benchmarking our actual requests at work, and this is what the output looks like it's very easy to read. (laughing) So, I wanna tell a little story about using this tool. We had a, this is amazing, this was amazing we had a page at work that would allocate 13 million objects per request. (laughing) 13 million. (laughing) And I mean this page would return in like a second so anytime anyone tells me Ruby is slow, I'm like what? (laughing) We did 13 million allocations and we're able to render that page in a second, like this is pretty good. (laughing) Anyway, I was able to get it down to about 300,000 objects for requests using these tools so we could monitor an actual request going through the application and get a real benchmark with it, and I wanna share the graph with you because it's extremely satisfying, so this is the graph and you can see where I pushed the production there it's very, very nice. So, the thing that I wanna do, the thing that I want to do with Rails 6 is that I wanna make performance easy. So we Rails, we make generating applications easy we make connecting to databases easy we make writing routes easy, generating views working with forms, writing tests, all of those things. But I think that now it's time for us to do the same thing with application performance analysis, I think we're falling down in that particular area of Rails. Yesterday Eileen said something that I really so first off, her talk was amazing, amazing! I said to her your slides look really, really great and she said to me, "You should go back to school." And I was like, "Whoa!" (laughing) "Look I, yes I did drop out of college but that hurts." (laughing) But yesterday she said she wants to make Rails scalable by default, but I think that this is really important this is a really important thing. But when I think about making Rails scalable by default I don't wanna make just the application itself scalable by default, I wanna make your team scalable by default I wanna make it so that not only is it easy get started it's easy for people who don't know much about Rails web applications to get started that's the way it is today. We can easily start a Rails application but I don't think that the Rails team is providing as much support as we can mid-career. So that's where I wanna start improving is making sure that we can actually provide tools for you to move from beginner on to more advanced. So for those of you like me who cannot hire somebody to fix their performance issues, you can just instead of rewriting everything in rust you can say like, "Let's take about five minutes "let's just take like a couple minutes here "and run this one command "and see what it says our bottleneck is "and maybe we can just fix that instead." So I wanna do for application generation or I wanna do for performance what Rails is already done for application generation. And I think the first thing that we're going to do is start by taking the tools that we use internally at GitHub for doing performance improvements and pushing those into open source, pushing those upstream. I have two minutes left, I wanna tell one more story I have one story to end with, and this is not serious at all so you can sit back and relax. For those of you that weren't here last year I need to do a little bit of ketchup (laughing) So, last year I gave a presentation that was much more of a waste of time than this presentation. So last year I spoke about Crap Cata and it was basically what it was, the talk where I presented this data here that I had acquired Crap Data. I talked about doing, analyzing this data and you know different types of analysis that you could do and 40 minutes later I revealed that I had acquired this data by a Raspberry Pi and a Motion Sensor that I had put together, where I had mounted the motion sensor above a litter box as such, and my cat would get into the litter box and then come out and I recorded all this data and made a log and this is well, I guess the cat made a log. (laughing) Poop jokes yes. So we had about 100 grams leftover. (laughing) So, it was literally crap data but anyway the actual reason that I built this thing is because last year around this time, we were moving, we bought a house. Like the day after I got home from RailsConf last year we had to pack up all of our stuff and we moved. So we got a house and my wife said to me "You have a closet full of electronics "if you do not use these electronics "we will throw them away." And I said to myself what can I do with it? (laughing) What could I do with a scale and a Raspberry Pi and a motion sensor? (laughing) And this is what I came up with. So let's talk a little bit about home automation because I mean the first thing that you do as a new homeowner I think, well at least is the first thing I do as a new homeowner so I'm like wow! I've got a house, I need to like automate everything, need to automate all of the things. So we have a toilet in the house as you do and unfortunately we have that like blue water in it, okay it's got like that blue cleanie water in it and the thing that's really bad about that stuff is it's basically Tide Pods for cats unfortunately my cat isn't as smart as a 13 year old and he tries to drink that stuff. (laughing) So I'm like okay, I need a way to solve this issue, the way I did it was I decided to automate my house I bought one of these, this is a door sensor, basically it's a magnetic sensor and on the inside it has a little reed switch so that's our magnetic reed switch on the inside. Now what I did is I took a tilt sensor, this is a tilt sensor I put my hand there for scale. (laughing) And basically I bypassed the read switch with the tilt sensor. So we have the Z-Wave sensor where imagine this is a side view, I've mounted the tilt sensor on it like that bypass the read switch such that when the Z-Wave sensor tilts like this oh so this is our on position. So when it's like that it's on and then if you rotate it like this it turns off, so it's as if you've opened the door for example. Then I decided to hook it up to a siren. (laughing) And I took a video to demo, I think it's running we'll see. So that's the siren there, there is a toilet when I lift the lid you can see the siren will come on. (laughing) And when I close it, of course yes good job editing Aaron yes it shuts off. So now I can tell when the toilet is open and closed. So, if nobody's in the bathroom we know to close the toilet so the cat does not go in there. Now aside, like one happy accident from this is that I actually have a log of all the times the toilet lid is open and closed. (laughing) And so I decided that Cambridge Analytica doesn't have enough of my information. (audience laughing) All right, anyway so. (laughing) I mean I wish I had implemented this earlier so that our cat didn't drink any of that blue water and get sick, but the truth is hindsight is 20/20. (laughing) Thank you very much, I hope I can see you again next year. (audience applauding)
Info
Channel: Confreaks
Views: 10,794
Rating: undefined out of 5
Keywords:
Id: cBFuZivrdT0
Channel Id: undefined
Length: 59min 29sec (3569 seconds)
Published: Thu May 17 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.