Resources

July 2023 Webinar: Shiny in Production

Shiny in Production Wednesday 19th July 2023 With Ryan Johnson, Data Science Advisor at Posit About the webinar: In this session, we’ll discuss how to create production-ready Shiny apps in R that can scale to thousands of concurrent users

Oct 10, 2023
55 min

image: thumbnail.jpg

Transcript#

This transcript was generated automatically and may contain errors.

Hi, welcome everyone. I'm just going to give people just maybe one minute just to finish coming in. It's only boring in the beginning anyway, it's only me talking. So I always forget to introduce myself whenever I do anything, including podcasts. So I'm Chris Bailey, I'm a data scientist at the Strategy Unit. And we have with us here Ryan from Posit, who's going to talk about Shiny in Production.

Yeah, I'll just wait until, oh yeah, I was going to say I'll wait until two minutes pass, but it is two minutes past. Okay, so welcome everyone. Just for the newcomers, just to introduce myself, my name is Chris Bailey, I'm a data scientist at the Strategy Unit. We have a webinar for you today. Just want to very quickly just give you some community dates for your diary before we get started. So we do have another webinar next month. We haven't yet confirmed the title and date, but do check on our website or on the Slack for more details. We have a workshop coming up, the highly popular intro to R and RStudio with the highly popular Simon Wellesley-Miller. He's a very experienced R coder, so I'm sure he'll do a brilliant job.

We have a conference coming up. So the actual dates are the 17th and 18th of October. That is the face-to-face conference. For the first time this year, we have R and Python talks together on the main stage, which is awesome. And we have various other events happening around it. So we have some online workshops where you can learn how to do stuff. I think, yeah, Ryan's doing something at the conference as well. I believe we're still in the process of organizing that. So, Ryan, that's all from me. So I'm going to hand over to, so Ryan, as I mentioned, he's from Posit. He's going to do Shiny in production. And I'm sure it'll be a very interesting talk.

All right. Thanks, Chris. I'll go ahead and start sharing my screen. I'm just going to dive right into it. All right. So like Chris mentioned, today's webinar is going to be all about Shiny in production. And for this webinar, this is really kind of just a sit back, take it all in, relax. We are recording today's session. So we can send that to anyone who couldn't make it today. But all about Shiny in production. I recognize a lot of the names on the call here. So it's good to see some familiar names. But for anyone that's new, like Chris mentioned, my name is Ryan. I work here at Posit as a data science advisor. And part of my role is just making sure that your team is familiar with all the cool tools that Posit's creating and also some data science best practices, which is going to be the focus for today.

For any questions you have, we're probably going to take the full hour with the content. So feel free to pop those questions into the chat. A lot of times, some of your colleagues might be able to help address some of those questions. But if we don't get to those questions, I'll be sure to copy those. And we'll try to follow up in an email. I also like to keep these sessions pretty casual. I'd say if there's any pressing questions, I'm totally OK if you just unmute yourself, scream into the microphone, ask your questions. I want to have a nice, fun, casual, and safe learning environment today. And I'll also share the slides for today so you can have that as a reference.

So what are we going to talk about today? Well, like I mentioned, this is all about Shiny in production. So we're going to start talking about what does it actually mean to productionalize a Shiny application. And then we'll spend the bulk of today talking about various strategies for creating production-ready Shiny apps, so benchmarking, profiling, and optimizing. And then once we're done, we have a little bit of time, we'll talk about how to host production-quality Shiny apps on Posit Connect and what you can do with those applications once they're on Connect in order to really make them shine. No pun intended.

Also within the slides here, and again, I'll share these with everybody afterwards, are some links that I used to create today's presentation, which might be helpful if you're looking to take your Shiny applications to the next level or design production-grade Shiny applications. But really, the holy grail for creating production-ready Shiny applications is this book right here, Mastering Shiny. It's a completely free online resource. And even though the title kind of mentions that's Mastering Shiny, kind of assuming that you have some understanding of Shiny, even if you've never used Shiny before or you're brand new to it, you'll still get a lot of value out of this book because it does walk you through the basics of creating your first Shiny application and then stepping through some more advanced topics.

What does "production" mean?

All right, so to kick things off, let's just talk about what does it mean to have something in production? I'm going to quote Joe Cheng, who's one of the creators of Shiny and also our chief technology officer here at Posit. And way back in 2019 at our studio at the time conference, he defined production as software environments that are used and relied on by real users with real consequences if things go wrong. So that's just one definition of what it means to have something in production. But there are various goals of a production environment. We need to keep it up, keep it safe, keep it correct, and keep it snappy.

So in terms of keeping it up, we have to make sure we prevent any unplanned outages, keeping it safe. We want to make sure that our data, the functionality, the code are all kept safe, unauthorized access within your firewall, your network, keeping it correct. Make sure it works as intended. Make sure that when you spin open the Shiny application, it's the same app today, tomorrow, a week from now, a year from now, 10 years from now. And then also making sure to keep it snappy, that it has fast response times, there's not a lot of waiting from the end user. And that's really what we're going to focus on for today. We could certainly talk about these other ones, but just for our hour we have today, we're really going to focus on how to keep your Shiny applications snappy.

The Shiny restaurant analogy

Now to help with my kind of presentation today, I'm going to make a fairly silly analogy, but I think it hopefully helps kind of explain how to make production-ready Shiny applications. And we're going to talk about the Shiny restaurant. So I really want you to put yourself in the shoes of a manager or someone who owns a restaurant. And when it comes to a restaurant, obviously, to create food, you need recipes. And a lot of these things in a restaurant, they actually translate pretty well over to the R ecosystem. And so when it comes to a recipe, when you think about R, this is essentially your Shiny application, the code itself. These are the instructions used to create your Shiny app.

Next in our restaurant, we have our chef. The chef is going to be the one preparing the food. For our Shiny apps, that is going to be the R session or process that's going to be reading this Shiny application to create that Shiny app. Next, we have our kitchen. This is where your chef lives, where they cook all the food. So this is going to be your computer, your server, anywhere you're running R. And then finally, we have our end product, our food. And that is going to be our rendered, our user interface, our Shiny application right here.

So let's say we have your grand opening of your Shiny restaurant. You've hired one chef, you have one kitchen, and in walks your very first customer. They order some food, let's say a cheeseburger, the chef cooks it, delivers it to the customer, and the customer is super happy. They leave the restaurant, and they spread the good word, they scream on the streets that your Shiny restaurant is the best restaurant in town. So a few hours later, you get some more customers. So now you have about six or seven customers, and things are still going great. Your single chef can take in all these orders, prepare the food on the single kitchen, and everybody's happy.

All right, all these customers leave, they start writing Google reviews, Yelp reviews. And now your restaurant's starting to get a little bit more popular. Let's say on the second day, now we have like 10, 15 people in your restaurant at the exact same time. And your chef's starting to feel a little pressure, getting a little stressed out, but they can still accommodate all the requests, all the orders from your customers. So things are great. More and more, the word spreads how great your Shiny restaurant is. The third day you're open, you get a line out the door around the block, everyone's pouring into your restaurant. And now you have like 20, 30 people in your restaurant at the exact same time. And at this point, your chef is like, oh, I am way overworked. I'm in over my head, kitchen's on fire. And this is going to result in some of your customers having a bad experience.

So as a manager or the owner of the Shiny restaurant, we need to take a step back and ask, what is the breaking point of our restaurant? So how many customers can visit our restaurant before things start to break down? And if we translate that over to our Shiny application, the question becomes how many users can visit our Shiny application before things start to break? And this process of kind of figuring out that threshold is known as benchmarking.

Benchmarking with shinyloadtest

Now for Shiny applications, some of you may have heard of this tool, but we've created a tool here at Posit called Shiny load test, which does exactly that. It tries to figure out how many people can visit your application at the exact same time before things really start to break down. So I'm going to very quickly just run through how you would run a Shiny load test. And in step one, you simply just have to run your application. This can be running on your local computer, or you can have it running in like Posit Connect or shinyapps.io. And once it's running, we're going to use Shiny load test to record your session. So you have it running, you record your session, and then you just interact with your application. You click buttons, you slide the bars, you generate plots. And once you're done, that creates a recording log file.

We then use in this third step, a second tool called Shiny Cannon. This is actually a command line interface tool that we've created. We feed in that recording log from step two, and then we can essentially simulate synthetic users to our application. So in this command, we're going to generate four synthetic users or simulated users, and they're going to interact with our application for about two minutes. And then once we run that command, we can essentially analyze the results.

Now there's a lot of different output from Shiny load test, but this is probably the most common output you'll see. So here we have this plot, it's a ggplot. On the y axis, we're looking at the number of simulated users. So we have our four users. And on the x axis, we're looking at time. In this case, it's going to be 600 seconds. And on this gray background, you're going to see all these various colored blocks in each row. These colored blocks represent time that the Shiny applications is essentially thinking. It's frozen in time, it's doing some type of compute step. And essentially, the user has to wait for the Shiny application to become responsive.

So really, the goal of Shiny load test and with these plots is to see kind of, you want more of that gray background to show through and you want these colored blocks to be as thin as possible. Because that's going to indicate that application is relatively snappy. Someone clicks on a button, and the application does a step and is then ready for more input. And just to give you a sense of what a very bad application would look like. Here we have a plot where we're actually simulating 16 concurrent users against 600 or so seconds. And here you can see that the colored blocks encompass the entire plot, we're not seeing any of the gray background showing through. So this is essentially an application that can't really handle 16 concurrent users. And so at this point, you know, you can start to get an idea of what that threshold is, we can see 16 is too much, we might want to backtrack it to figure out what is our kind of maximum number of concurrent users. So that's Shiny load tests.

Profiling your Shiny app

So let's go back to our Shiny restaurant. And let's say we've done the benchmarking. So we've determined that, yeah, we can have like five, six, seven or so customers in our restaurant. And our chef's going to be happy, our kitchen is going to be happy, this is a good spot. But by benchmarking, we've determined that 20 or so is too much. All right, this is at this point is when the chef gets overworked, the kitchen bursts into flames. And so that's too much. So we find that sweet spot.

But then we start asking some questions like, you know, well, how can I then support 20 customers into my restaurant? You know, 20 is a no, I want more customers, I want to be able to support more and more people and make more money. So how do we accommodate 20 customers? Well, we have a few options. We could again, as a manager, we can think about maybe adding more kitchens to our restaurant. Alright, so we can have three kitchens here and just a single chef. And essentially, they can multitask, right, so they can have multiple meals being prepared at the same time. Maybe we hire more chefs. Alright, so now we have three chefs in our kitchen, all sharing the same kitchen, which is probably fine, you can multitask a bit more here. Maybe we do both, maybe we hire more chefs, we build more kitchens. And yeah, this is absolutely going to be able to accommodate more customers.

But it's worth noting that for these two options, it's going to cost money, you know, to hire more chefs and to build more kitchens. And so for our Shiny application, that's essentially having more R processes, or more servers, more nodes, more computers supporting this application. And that costs, you know, not only money, but compute resources as well. So for today's webinar, I really want to focus on, you know, looking at our chef and asking the question, like, can we actually just improve the performance of our single chef? And how would we do that?

So again, you're the manager. So what you do is you take a chair and you plop it in the kitchen, and you just sit there and you watch your chef. And you have a stopwatch in your hand. And as they go through the various steps of preparing a meal, you just clock how much time each step took a little bit of micromanaging, but bear with me here in this analogy. So an order comes in, your chef collects the ingredients, then prepares those ingredients, cooks up the meal, assembles it, and then serves that finished product. And again, as you go through each step, you determine how much time each step took. Some steps go very quickly, some take a little bit longer. So kind of cooking the ingredients, that's our rate limiting step here. So this process of essentially figuring out every single step of your cooking process and how long each step takes, this is known as profiling.

And we can do this with our Shiny application as well. And before we actually talk about profiling, there's two rules here for profiling your Shiny application. And the number one rule is don't guess. Again, referencing Joe Cheng, our chief technology officer, even though you built a Shiny application, and you may know exactly what is the slow step, you still don't have to guess because there are tools out there that will tell you exactly what is slow in your application. So some people might just say, Oh, I can know just using my intuition, I can think that this is probably the slow part of my Shiny app that I need to fix. But Joe Cheng says, you know, your intuition stinks, I don't even know you, but it stinks. And essentially, what Joe is jokingly saying is that you don't have to guess, you don't have to use your intuition, we have tools that will tell you what parts of your Shiny application are slow.

But Joe Cheng says, you know, your intuition stinks, I don't even know you, but it stinks. And essentially, what Joe is jokingly saying is that you don't have to guess, you don't have to use your intuition, we have tools that will tell you what parts of your Shiny application are slow.

So the number one, number two rule is to use a profiler. And probably the most common profiler for Shiny applications is a tool called ProfViz. So here in this GIF, I have a Shiny application that I'm opening up, and I'm just interacting with it as normally. But behind the scenes, I'm running this ProfViz tool. And so once I'm done interacting with it, it generates this report where we can see every single step in that Shiny application, how long each step takes and how much memory is being allocated or deallocated.

Just to simplify things, here is another ProfViz report. I'm not looking at a Shiny application, I'm just looking at a function, or actually four different functions. So same rules apply. In this ProfViz report, we have four functions starting here on line four to line seven, we're calculating the means using four different functions, we have apply, call means, lapply, and vapply. And what ProfViz does, it'll tell you exactly how long each function takes to run, you can see that right here. And you can also see how much memory is being allocated or deallocated along the way. So this is interesting, because even though these four functions do the exact same thing, just calculating the column means, you can see the vapply function is actually 10 times faster than the apply function, and utilizes hardly any memory because it's a vectorized operation.

Optimizing your Shiny app

Alright, so now that we've profiled our Shiny application, right, and then going back to our restaurant, you start thinking of some ways of how we can improve our chefs kind of operations. So maybe we have the chef prepare the ingredients in advance, or maybe someone else in the kitchen can prepare those ingredients in advance. That way, not to spend the time preparing them as orders come in. Maybe we just keep the kitchen organized and updated, making sure the chef knows exactly where all the tools are, and making sure they have the best tools for the job. Prepare the most popular meals in advance. And I'll talk about this one here in a little bit. And then also multitasking, like training your chef to prepare two, maybe three meals at the exact same time.

So we're going to go through each one of those steps, we're going to talk about it in the context of our Shiny restaurant, and then how that translates over to our Shiny application. And we're going to start with preparing ingredients in advance. So again, let's say your chef, they get an order for a ham and cheese sandwich. And when that order comes in, if this is their starting material, this raw material, they're gonna have to spend some time cutting tomatoes, slicing the bread, slicing the ham, and that's going to take time. And what would be much faster is if this was their starting material, all the ingredients were prepared in advance, they can simply grab each component, assemble the sandwich super quick, and then deliver it to the end user or the customer.

So for Shiny apps, this essentially means preparing your data in advance. And 99% of all times when you have slow Shiny applications is likely because the data is too large. And you know, sometimes that's going to be kind of unavoidable. But other times we can prepare that data in advance, so that only the data that's needed for that Shiny application is processed. So a few different things to consider. Make sure when you're reading a data to your Shiny application that you read and process that data only once. You want to try to not get into a situation where every single time someone slides a bar in your Shiny application, it re-reads in the data, and then re-reads in the data over and over and over again.

You should also think about, especially large data sets, having that stored somewhere else and not within your Shiny application itself. So maybe have it stored in a database. Or maybe move any data processing steps. So if you have to like transform your data or even model your data, you can actually try to move this outside of your Shiny app. You can have it within a pin. And so if you've never heard of pins before, it's a way to take data and store it either at like a S3 bucket, Bitbucket, Posit Connect can also store pins. Or you can take some of that data processing and you can put it into an R Markdown document and schedule that to run on Posit Connect. Or you can serve it as a Plumber API. These are all great options. Really the take home here, Shiny is a visualization tool. It was never quite designed to be a huge compute workhorse. And so anytime you can take these data transformation steps and move it outside of the Shiny application, the better.

All right, so going back to our Shiny restaurant, another way that we can improve operations is to keep our kitchen organized and updated. So I've had to ask everyone here on the line, which kitchen would you prefer to work in? Would you rather one on the left where everything's nice and organized? Or do you prefer the one on the right where it's just a little bit more cluttered and disheveled, and you may not know where everything is? Now I'm going to guess you'll probably say one on the left. And let's say our chef, they said, hey, I need a new knife, a new kitchen knife to prepare the meal. As a manager, it would be a very poor decision to provide them with a very rusty, old, dull knife, or even worse, provide them with a medieval sword, which is not the right tool for the job. And essentially, we want to provide them with a very sharp, clean kitchen knife so they can do their job correctly.

So when it comes to our Shiny application, this means optimizing and organizing your code and functions. And there's a lot of different ways you can do that, and I'm just going to talk about a few. You always want to make sure that you're using the fastest functions, and you may be asking yourself, like, how do I figure that out? Like, well, Profiz is one way. You can kind of take a bunch of functions and compare them to the other, but just some other tools, including the very fantastic Bench package, which has a mark function, so you can benchmark various functions. We'll see that here in a second.

You also want to make sure you call code in the correct spot, and I've alluded to this briefly about making sure that data is not being re-read in every single time someone interacts with your Shiny app. Avoid copying your code. So this is just a way to keep your Shiny applications organized, so putting code into functions, for example. And R is great for vectorized operations, so any time you can vectorize anything, that's certainly going to be the way to go. And then, if you haven't heard of Shiny modules, it's a great way to just organize your Shiny applications to separate modules, which can make troubleshooting and, again, just kind of consuming your applications significantly easier.

So let's talk about a few of these steps, and we'll start right here with using your fastest functions. So in this slide, at the very top, we're seeing two functions, all right? Function, this first one, is simply just wrapping up the mean function. So we're finding the mean of a vector x, and we're going to call this function mean1. In the second function, we're going to call mean2, does the exact same thing, but we spell out mean, so we take the sum of x divided by the length of x. These will do the exact same thing, just two slightly different ways.

And then down here, we're going to run the bench, the mark function, so benchmark, and we're basically going to compete mean1 versus mean2 and see which one's faster. You can get a lot of different output from this bench package and the mark function, including how the kind of the average minimum time, median time, and iterations per second. And it's pretty surprising, you can see that mean2 actually outperforms just the mean function. Now, I'm certainly not saying to go back into all your code and swap out mean for sum divided by length, because mean is a little bit easier to read, it's a little bit more concise. And, you know, just a couple of hundred microseconds, maybe that doesn't make that much of a difference. But, you know, there's certainly ways where you could think about, you know, maybe taking one function and competing it against another to see which one might be faster, especially if you use it multiple times in your Shiny application.

Now, you also want to make sure you're calling code in the correct spot. So let me go through a quick example of where this might kind of show us heading into your Shiny applications. Here we have a function I'm going to create called myDataPrep. And it's just a made up function where the first thing it does is it reads in a CSV file, so some data, and then it takes that data, it filters it for some non important variables, it groups it by some other variables, and does some other slow functions.

Now, what you don't want to do, and what I've alluded to before is have this function like this that reads in data somewhere in like a reactive function in your Shiny app, because essentially, anytime someone interacts with your app, and it calls this reactive function, it's going to reread in that data over and over and over again, which is just not necessary. So what we really want to do is take this myDataPrep function and move it outside of those reactive steps outside of your server, so that essentially, it's called once and only once.

And then we have Shiny modules. Now, Shiny modules, some of you may use them today. But if you have any applications that are really starting to grow in complexity, it's becoming harder to manage to keep track of, you should really think about organizing your Shiny application and what's known as Shiny modules. So I'm not going to go into full depth of how to actually create Shiny modules, we have some great documentation on our Shiny website. But just to explain kind of conceptually what they are, let's say on the left hand side, this is your Shiny application, and each of these box corresponds to a different step. And you can see here just reading through it's a bunch of different steps, not an overly complex Shiny application. But you can see very quickly that certain steps are having impacts and interacting with other steps, arrows are starting to cross over each other, and it's starting to get a little bit complex, which then becomes harder to troubleshoot.

And so what you can do is you can essentially break up the Shiny application into separate modules. So here we have a session module, an HPC module, data module, and a results module. And these are independent modules, and you can troubleshoot just a single module. And what's also really nice is you can take, for example, this data module, and you can incorporate it into a separate application. So they're portable between applications as well.

Caching outputs

All right, let's go back to our restaurant and talk about another way that we can hopefully improve performance. And that is to prepare the most popular meals in advance. So I think the best way to think about this, let's say your Shiny restaurant is actually a pizzeria. And let's say if 10 people walk into your restaurant, nine of them are going to order your world-famous margarita pizza. So because of that, you start to think, well, okay, maybe I can start preparing a bunch of margarita pizzas in advance when I first open up my doors. And that way, when people enter the front door, I can just quickly deliver that already prepared margarita pizza. So essentially, you can have all these margarita pizzas in this hotbox, ready to be delivered instantaneously.

So for Shiny applications, this essentially means caching your outputs. And some of you may be familiar with this, but what is caching your outputs? What Shiny will do is it'll store that combination of your input and outputs, so kind of the output of these render and reactive functions. And if the application, if it sees that input, it'll generate the resulting output only once, and then it will store that output in a cache. And if those inputs are seen again, it can simply retrieve that value or that plot from the cache instead of having to recompute it over again.

Let me show you kind of conceptually how this works. So here we have a render plot function. So I'm generating a plot here of the state of Maryland, which is where I live here in the United States. And to generate this plot, it just takes a single input right up here, where I input the state Maryland. So let's say this takes about four seconds to compute. Not super slow, but you know, still four seconds, the user is going to have to wait. So what I can do instead is I can take this render plot function and I can pipe it into the bind cache function. It's going to take that single state input. That way, when I go to indirect to this application, I select Maryland, it generates this plot once, which takes four seconds, and then it caches that plot. And then if another user or my future self comes back to this application and selects Maryland, rather than having to regenerate the plot, it can just grab the plot from the cache and deliver it in a fraction of the time.

Async programming

All right, and now we have multitasking, which is going to be our last step we'll talk about. So it'd be really nice if your chef at the exact same time could have a turkey going in the oven, maybe some pasta sauce on top of the and on the range here, right next to some stir fry, all going at the same time preparing three different meals simultaneously. So what does that mean for Shiny apps? Well, it means using something called asynchronous programming, which sounds pretty complex. But the reason why we have to do this async programming is because R is inherently a single threaded programming language, meaning that it's kind of hard to do two operations at the exact same time. So to implement this async programming, we have to use the help of some packages, specifically promises and the futures package. There's also another way you can do this by essentially offloading any of these steps to something like an API, which can be running on a separate R session outside of your Shiny application. This is optional, but it's always good practice again to move any of those data processing or compute steps outside of your Shiny app.

All right, so let's talk about how to actually leverage async programming specifically as promises package. And we're just going to use this example Shiny application I have here, which I'm going to call analyze data app. This application does two things, it takes in some inputs, and you can either plot the data, which goes very fast, it only takes about a second to plot that data. Or you can run a model on that data, which takes a little bit more time to run about 60 seconds. And again, let's say this app is using a single R session or an R process.

Some of these users, they log in, we have four users here, and the only thing they want to do is plot some data. And they're happy because this step is very fast, they click plot data, and within a second, that plot is returned to them. Similarly, maybe you have a Shiny application that, you know, a user comes in, and they want to run the model. And so this takes about a minute. And the user knows that they know it's going to take some time. So they click run model, they go grab some coffee. And when they come back, the models trained and ready to be interacted with.

But here we can get into an issue where let's say you have a user come in, and they want to run a model. So they click model. And then right afterwards, you want to have these users come in and plot some data. But now they essentially have to wait for this model to complete before they can plot the data. So something that used to take less than a second can now take, you know, over 60 seconds. And this is going to result in these users being very confused of why is it going so slow, and they're not going to be happy.

So instead, we can implement something known as promises in this async programming. And we essentially make our slow running steps a promise. So we tell this user that wants to run a model that, hey, your model, it's going to take some time, and it will eventually complete, I promise you, it will complete. But what this allows you to do is to also serve those fast running steps instantaneously. So user comes in, they run the model. And if these users log in, essentially, the model pauses, it serves these users, because the steps are very fast, one second, and once they're done, it can come back to the model and finish that step.

Putting it all together

All right, so we talked about a bunch of different ways to how to improve your Shiny application and make them slightly more production ready. Let's just review some of those steps. And here I'm just going to use this example Shiny application where we have the UI here in these red steps, server in the yellow, and then we have some attached data to the Shiny application. The first thing we talked about was preparing your data in advance. You can see right here in our server function, we have a step to clean the data. Well, if you don't need to have this in your Shiny application, you can certainly remove it, put it inside of an R Markdown or a Quarto document, scheduled to run elsewhere, have it in a pin. And then also, again, anywhere and anytime you can have your Shiny application not necessarily be bundled with the data itself and have that data stored elsewhere, either in a database or a pin or a plumber API, that is certainly going to be our best practice.

Next, we talked about how to optimize and organize your code and your functions. So one thing we, in this silly application, we're using this read.csv function, which is a great function for reading in CSV files, but it's not necessarily the fastest one. And there's other functions out there that can be used, including this fread function. And then we can also think about taking some of these steps and moving it outside of any reactive functions. So again, you're not rereading in the same data step over and over and over again.

Then we talked about caching your output. So that pizzeria example. So if you have any outputs of your Shiny application, like the render outputs, and those outputs kind of are dependent on a certain combination of inputs, it can essentially generate those. It can see those inputs, generate the output, and then store that output. So if it ever sees that combination of inputs ever again, then it can simply retrieve it from the cache rather than having to compute it all over again.

And then finally, we talked about async programming. For our Shiny application here, we have two steps. It creates a model. It also renders the plot. But the modeling step, this takes a long time. With the rendering a plot, that goes really fast. So essentially, what we can do is we can promise the user that the slow running steps will eventually finish, but it will allow the fast jobs to complete first.

Hosting on Posit Connect

All right, so those are just a few of the steps for creating more production-ready Shiny applications. It's certainly not an exhaustive list. And again, that Mastering Shiny book I mentioned before, it will also be a great resource for creating more production-ready Shiny apps. But there's just one more thing I want to talk about, one more tool to really host production-ready Shiny applications. And before we talk about it, which is Posit Connect, let's go back to our Shiny restaurant analogy. And again, for the most part, we've been talking about you being the manager or the owner of your Shiny restaurant. And as the owner, you have some important decisions to make, such as how many chefs are you going to hire, and how many customers can come into your restaurant and still be like a performant restaurant, make sure all the customers are happy. So to help with these important decisions as it pertains to your Shiny application, your manager essentially can be Posit Connect.

And if you're not familiar with Posit Connect, this is our professional publishing platform where you can take your Shiny applications, publish them to Connect, and then share them easily with whoever needs to see them. And Posit Connect can host a lot of other things besides just Shiny. So it can do Shiny for R, Shiny for Python, Markdown, pins, plumbers, some of the things we talked about today, as well as a lot of other content in the Python ecosystem.

So let's talk about how Posit Connect can help host and serve production-ready Shiny apps. So going back to our Shiny restaurant, again, you are the manager and you determine that you're going to hire one chef. And this chef is going to manage 10 customers, maximum. They can have up to 10 customers they can manage. If they have 11, things are going to start to break down. So in this configuration, when we think about Shiny, this essentially means we can have one R process and we can have up to 10 concurrent users.

Now, once a Shiny application is hosted on Posit Connect, you as a publisher have access to these runtime settings. And there's going to be three settings we're going to talk about today, max processes, min processes, and max connections per process. So in this configuration, we have one R process, which is set right here. We can have up to one chef or one R process supporting this application. And we can have up to 10 customers. But in the Shiny world, we call those connections to your Shiny app. So max connections per process, 10, one chef, one R process.

This may be perfectly fine for what you need for your Shiny application, but maybe you need to tweak things. So let's say to maybe improve the performance of your Shiny applications, you determine you're going to hire another chef for your kitchen. So now we have two chefs and we essentially split the load. Chef one can have five customers, chef two can have five customers. So that essentially means each R process can have up to five concurrent users. So we can tweak those runtime settings to meet these needs. So we can have up to two R processes, and we can have up to max five max connections per process. And two times five is still equal to 10. So we can still only have 10 concurrent users for our Shiny application. But again, we can always tweak that.

Now there's one additional setting here I haven't yet mentioned called min processes. So let me kind of explain what this means in context of our Shiny restaurant. Let's say your restaurant is 24 seven, it's open up every single hour of the day. But you know, you probably imagine one o'clock in the morning, two o'clock in the morning, there's probably not going to be too many customers. But what you don't want to have happen is someone walk in at 2am, and they order a cheeseburger. And the chef essentially has to turn on the burners again, thaw out the ingredients, you know, cook the burger and then deliver to the customer, which takes a long time. It will be much better is if those, you know, all the equipment was turned on and ready to go all the ingredients were already prepared. And the chef can essentially cook the burger very quickly and deliver it to that customer, even in the wee hours of the morning.

So essentially, we want to have the kitchen always on, we want to have the chef always ready to prepare that food. And we can do that by setting min processes to one. And that essentially means that one of these are processes never goes to sleep. So the Shiny application is always running 24 seven. This is a great way to configure your Shiny applications on Posit Connect. If your Shiny app has a long startup process, right, so maybe when you kick on a Shiny application has to read has to read in a bunch of data, maybe has to train a model before you can even interact with a Shiny app. So if that's the context of your Shiny application, then you might want to have it always on. So that startup process is completed once and only once.

And then just one more configuration. Let's say your Shiny restaurant is a VIP five star restaurant. And you determine that you want to have for every single customer that comes into your diner restaurant, they have a dedicated chef, right? So one customer one chef. So in this configuration, we can have up to 10, our processes are 10 chefs. And we're only going to have one connection per process. This is truly the VIP experience, everyone has their own dedicated our process. But it's just worth noting that the more our processes you have, that's the more compute power needed on your connect instance. So if your server can handle it, this is certainly going to, you know, ensure that the Shiny application is as performant as possible.

Recap and Q&A

Alright, so that's pretty much all I wanted to talk about today. I definitely wanted to leave some time for questions here. But just to recap what we talked about and how to improve your Shiny app performance to make it snappy, make it more production grade. We talked about benchmarking and profiling your Shiny app. And then we talked about all the ways you can optimize preparing your data in advance at async programming, caching, and also just quality checking your code. And then again, if you have Posit Connect, it's a great way to tweak those runtime settings to make sure that you know, depending on your applications needs, you can always allocate more our processes and kind of dictate how many connections per process.

Alright, so with that, I think we have about 15 or so minutes, and I'd be happy to take any questions. Feel free to pop it into the chat or you can just simply come off mute.

Gotcha. And I'll try my absolute best to try to answer these questions, but I may also need to tweak our Shiny team. Let's take a look here. All right, can you catch all potential options or the cash only created when a user starts using the app? So caching, it's, it's something that needs to be thought about as you're designing your Shiny application. If your Shiny app has like a set number of outputs or combination of outputs, maybe that's like 10 different different plots that can potentially generate. And maybe those plots take a little bit of time to compute. If it's only 10 options, that's going to be like a great option for caching, because there's not going to be anything else. But if your application has an infinite number of combinations of a plot that it can generate, that may not lend itself well to caching because as it catches going to grow in size, it's going to be more kind of memory and CPU. It's going to essentially be consumed for caching all those outputs. So I wouldn't necessarily say caching all outputs is a great option, but thinking about which ones have a finite number of potential combinations will certainly lend itself well to caching.

All right, Rodrigo, here we go. Why call benchmarking and not load testing when you're measuring how many users can access your application? Rodrigo, do you mind kind of explain that question a little bit? I think I know what you're referring to, but I just want to make sure I... No, it's just a terminology question. Like, usually I understand benchmarking when you're comparing two things. But in this case, we're just measuring the performance of one thing. So I just didn't get why it's called benchmarking and not load testing. Well, yeah, I think in this context, I may have been jumping the terminology a little bit, but load testing, benchmarking would essentially be the same thing in this context. Thanks. That's a good question.

Any more questions? I've got a question. Chris, your audio is coming through really quietly. Sorry, I moved my microphone because I was eating before. Yeah, just while people are thinking of questions, I've got a question. So I know on the Posit Connect, I can't remember what it's called now. There's a little slider where you can choose how it manages. What's it called? Is it called load factor or something? Yeah, yeah. Let me bring up a Shiny application here and I can see what you mean here. We actually changed the way that it looks, but yeah, we do have a load factor.

All right. So this application, it's booting up, but here I'm showing that same runtime settings. It looks a little bit different just because we've updated the interface with Posit Connect, but essentially those settings we were talking about are right here. So we have min processes, max processes, max connections per process. And as Chris mentioned, we also have something called load factor. Chris, are you just asking about how does this load factor work or? Well, I just thought, I think it's a really interesting idea and I sort of get it, but I just thought, I wonder how, if you could, my basic question is, how much thinking will it do for you? Do we need to actually not really worry about the other settings? Is it good enough to kind of really get it right every time or is it, you know, a kind of halfway house?

Yeah, I think it's, we want to make sure we give as much power to the publisher as possible to really tweak these settings and not have Connect like, you know, think it knows what it's doing for you. So a lot of times this is going to be kind of configured for you, or sorry, you're going to configure it and you can kind of gauge the performance and make any tweaks. But just to explain what is going on with this load factor, and this application is not booting up, it probably worked at some point, but we can still talk about this. Essentially, if we go through an example here, let's say we have three max processes and we can have up to 20 connections per process. So we can have 60 concurrent users to this application. So the question becomes, you know, if I share this application with one person, you know, obviously that's probably going to boot up one hour process because we can have up to three. So we just have one. If I share it with a second person, do they then share that process or does it boot up that second, you know, R process? At what point do we kind of make that threshold to open up a new R process?

Currently it's set to 0.5. And that means that once this number, once an R process gets to about 10 or so concurrent users, it's then going to switch over to a second process. So that's kind of that load factor. Once you hit halfway here, it then spins open that other process, but you can change that. So you can make it, if I set this to one, for example, that essentially means that as soon as that second person logs in, it's going to spin open that second process. The third person, the third