WebObjects • 53:14
Complex applications require optimization for maximum scalability and performance. This session discusses the tools and techniques used to collect and analyze application performance and identify areas for improvement.
Speakers: Brian Fitzpatrick, Chris Miner
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good afternoon. It's good to see so many people here who have checked out of their hotels looking for a nice, cool, dark place to catch some rest before they take their flight back to wherever it is they came from. Have you guys caught all the other WebObjects sessions this week? Catch some good sessions? Who caught... Who caught Steve Heyman's presentation? Come on.
Steve Heyman? And then we had James Dempsey singing, right? Okay. Well, are there any Canadians in the room? Yeah? Well, I'm not Canadian. I'm from New Orleans, Louisiana, and I'm not going to sing or play the guitar or anything. We're going to talk about some really interesting stuff about WebObjects. Now, a guy came up to me yesterday and he said, "Fitz, what is it that makes WebObjects, an application server, better than all the other application servers out there?" And I said, "How should I know, Bob? You're the product manager." Thank you for your jaded applause.
Anyway, we're going to talk about making your WebObjects applications faster. We have a couple of goals. We're going to finally eliminate performance bottlenecks in your components, in your application, in your enterprise object model. We're going to talk about three areas. We're going to talk about the tools needed to identify trouble spots, including tools that ship with WebObjects. We're going to talk about certain types of bottlenecks to look for in your WebObjects application. And then we're going to talk about certain techniques which you can use from the get-go that will eliminate bottlenecks.
We're going to talk about when to do performance tuning. Do you do it now? Do you do it later? Do you wait till you finish your app? Do you start from the beginning? We're going to talk about some tools that ship with WebObjects that make things a lot easier. Playback. WoEvents, WoEstats, other methods to measuring performance of your app.
We're going to discuss specifically WebObjects and UOF techniques to improve your performance. There's a lot of other techniques out there which we're not going to focus on related to programming in general, but those are for definitely another session. And last, when it's time to upgrade your hardware. When it's time to get more RAM in that machine, when it's time to get more processors, when it's time to increase your processors. Fortunately, WebObjects, I'm sorry, increased the number of machines you have for app servers. Fortunately, WebObjects makes it really easy to add machines to your server farm.
Now, I'm a big fan of Donald Knuth. I used to be a big LaTeX fan about eight or nine years ago. Anybody else here use LaTeX? Way back in the old days when XML wasn't even really the cool buzzword yet. Premature optimization is the root of all evil.
Now, I put this in here specifically because that is one of the things that I am most guilty of. When I sit down, we get a design for application, we start coding, and I just want everything to be really pretty. We've got a nice clean slate. I want it to be fast. I want to make sure I don't do anything that is a performance bottleneck. Unfortunately, that's not the time nor the place for doing that kind of work.
You want to work towards functional stability. You have a specification, you create your application. You want to get functionally stable before you start optimizing things. Because you never know when you're going to add or remove functionality. You may optimize something that doesn't even wind up in your final application. You may optimize something that is buried in a myriad of other heavy duty methods and it turns out that it was just a waste of your time.
You don't want to sacrifice good design and maintainability for optimizing. You don't want to wind up loosing everything into a single method that is 375,000 lines long just because it goes 10 milliseconds faster on your little performance meter. Speed is not everything. Optimization is about making your application faster but making your development time just as fast. It's also about maintainability. Another thing I'll talk about is a big carpenter adage which is measure twice, cut once. I usually cut my finger first. You want to measure, modify, and then measure again.
So create a controlled environment. This is really important. Create a controlled environment. Do a measurement. Make a change that you think might be an improvement. Run your controlled test again with no other change in it. And then you can get conclusive evidence as to what helps and what hinders.
Our technology framework provides a number of different very handy things to help you measure your performance. WoEvent profiles methods in your components. It's an extremely precise method for measuring what parts of your application take more time. There is also WoeStats. WoEstats is a very, very enlightening thing to have on your application. You can pull that up and tell at a quick look on one page what components are faster, what components are slower. You can see mean time. You can see the slowest access or the fastest access. It tells you a lot about what your components are doing.
The Playback Manager can simulate multiple users. You set an NSUser default in the command line, run your application with the recording enabled, and then when you quit the application, then you can play back this session with one user, simulate 10 users, simulate 200 users hammering your application. Lastly, and one of my personal favorites is SQL debugging.
We all know that EOF generates SQL to talk to your database. I'm really happy to say that I haven't put a line of SQL in an application since I started using WebObjects. And that was one of the things that really made my life hell before I discovered this technology.
This shows you exactly all the SQL that WebObjects generates and sends to your database, as well as information about what WebObjects gets back. WebObjects gets back commit messages. It gets back, it says, beginning internal transaction, finishing internal transaction. It'll tell you how many rows it gets back. It'll tell you when it's committed changes to your database.
So before we get started measuring and testing, we need to come up with a test environment. So you want to have your test environment mirror your deployment environment. If your deployment environment is two Sun Enterprise 10,000s with 64 processors and 4 terabytes of RAM, you don't want to make your test system an old G3 that you found in the back closet with about 32 megabytes of RAM running something completely different. You want to go for the same operating system. If you're deploying on Solaris, test on Solaris.
If you're deploying on Mac OS X, test on Mac OS X. Don't switch and modify and change your environment in midstream. You want to attempt to mirror the number and kinds of servers. Now, this is something that is a little difficult to do, especially for really large-scale deployments. If you've got a half a million dollars worth of big iron you're running your deployment on, you obviously can't afford that for your test environment. However, what you want to do is try and mirror it as closely as possible. Try a particular scaled-down test environment of your deployment environment.
And lastly, you want to isolate the test environment from your users. You don't want to have your test environment running in the same segment of your LAN as your production environment. You don't want them sharing the same database, for example. And I've seen that happen where a test environment uses the same database as the production environment. You're not going to get, you have so many variables other than what you're testing, you're not going to get any conclusive results at all.
Now, so we've got our test environment set up. We've gone out and bought more hardware. Now we're going to determine what pages are important. You have an application with 20, 30, 40, 100 pages. Make a list of the pages. What pages are going to be frequented every time someone comes into the app? For example, your main page, that's something you should work at to make blisteringly fast because first impressions are paramount.
When a user comes in and they see your application's front page takes half a minute to load, no matter how fast the rest of the app is, they're still going to be left with that initial thought like, God, I got here and it took me half an hour to get to the front page.
Establish acceptance criteria. What you want to do is decide what is acceptable and acceptable speed for my application. If you decide that you want every page to return in half a second, well, then work to meet that goal. If you want every page to return in a minute and your users will put up with that and not go elsewhere, well, then that's a goal to work for too. You might want to work on a big query page. Say you have a query page that returns thousands of objects or raw roads, as we'll discuss later on. Maybe that should return in five seconds. There are certain cases where a longer wait is acceptable.
Decide how many users you want to support concurrently. Is this application a small data entry app for a small company where you're going to have 20 or 30 people working on it a week? Or is it a large online store or an online presence where you're going to have hundreds of users hitting the app at the same time? Determine what you want to test for.
Next, create test cases which exercise the important pages. Chris is going to give us a little demo of using Woe Playback and then checking with Woe Stats and Woe Events to see the speed of certain pages. So you might want to create specific isolated cases for certain pages that are critical to your app.
Okay, now we're going to talk about preparing your test cases. Enabling recording for your application. The Woe Recording Path. So if you have your application, you've finished your feature complete, you're ready to do a little bit of optimization, you set the command line argument, Woe Recording Path, you set it to a directory. In our case, we use temp slash foo dot rec. This is our directory where the WebObjects Application will write out a series of transactions, requests and response, which records your visiting the app.
Manually execute a test case. Set up that variable, set up that NSUserDefault in your application, launch the application, execute the test case, stop the application. Now you can repeat this with different directories for each test case. You can point the Woe Recording Path at different directories and say this test case is going to test the big fetch page. This is going to test the main page. This is going to test a simulated user walking through the application, finding something and then going back to another part to purchase. purchase something, for example.
Now, when you run your test cases, so now that we've enabled that user default, we're going to pull that off of the command line, and we're going to run the app like we would run it in deployment. Now, there's certain defaults that you're going to want to enable or disable when you deploy your application. First is Woe Caching Enabled.
Woe Caching Enabled, which defaults to No, is what allows you to do rapid development on your application. It allows you to make changes to your .wad and your .html files, save them, and reload them in your browser and see it immediately. By setting this to Yes, WebObjects knows that it's safe to cache these templates in memory. So you'll get a small hit the first time WebObjects reads the templates off the disk, but it will cache them next time it will just follow a pointer, a reference. I'm sorry. In memory to retrieve that information.
Woe debugging enabled. You want to set that to no, especially, most especially, if you use woeapplication.debugstring. That's a great method to use. You can put tons of debugging print statements in your application, which you'll see in your console. And then when you deploy, kick this over to no and go your merry way. You won't see any of those. You'll still see log strings. You'll still see printlns and whatnot, but it'll turn those off for you.
Last but not least, and it's something that I want to say with a little bit of a caveat, is Woe allows current request handling. That defaults to No. Now what this enables, if you turn this on, is multi-threading in your application. This can provide a dramatic speed up, but I strongly, strongly encourage you to be familiar with the caveats of multi-threaded programming before you turn this on. In my experience, multi-threading bugs are extremely difficult to track down and, believe me, it's something that you don't want to end up working with if you're not familiar with multi-threading programming.
So now that we've got all that set up, we're going to disable the recording, start more than one instance if you're looking at a large deployment, start one instance if you're looking at a small deployment, whatever your test environment is. Run the playback, run one playback concurrently, two, run one at a time, run them serially. Let the system achieve steady state. Now this is very important.
You have to remember that your application contains methods that start and stop. A new session is created and it lives for a certain period of time and dies. The session death and birth are something that's very important to your application because your application is going to run all day, 24/7. So what you want to do is let it reach a point where sessions are expiring and new sessions are being created. So it will create a sort of a steady, receive a sort of a steady state.
So we've run our application, we've done our playback, we've been recording, we've done our playback. Now we're going to take a look and see what kind of information we can find out about this application. First we're going to look at a couple of pages. We have Woe Stats and Woe Events set up.
Now, these are direct actions. They are out of the box now, disabled. You need to set a password for them to use them at all, which we've done in our sample app. And the Woe Stats is something you can just go to at any point after you've hit the app, and it's going to give you this aggregate data that's collected about your application on a component level.
Now, a component, as we know, can be as big as a page, as small as a character. So, it'll give you information on all of those. You might want to pay attention to the page ones. You might want to pay attention to a certain subcomponent that's taking a lot of time.
Configure event recording. Now, event recording is something you need to go in and turn on. There's several different buttons you can set to say, these are the kind of events I want you to gather information about. WebObjects can amass an incredible amount of events in a second. I don't even remember what the number is. Maybe NQ and one of the guys from engineering can say. But it's blisteringly fast and has very little effect on your application.
So, you go to World Events Setup, say, tell me what these events are, do your playback, and then you can come back and look at the World Event Display page to see sub-transaction times all the way down to the method granular level. Now, for those of you who have been around doing WebObjects work since the old days when we used that other language, anyway, there were tools available for you called gprof, gmon. You'd have to build your app in profile mode.
You'd have to run your application. You'd have to quit your application. You'd have to run it through. You'd have to run another program. And you'd get this really nice information that told you about this. Well, that's really great, but that's something that you had to prepare for. You had to sit down. It was very time consuming. This is something that happens on the fly, in the product, out of the box.
So we've done that. Now we can use WoeStats to find the pages that don't meet our acceptance criteria. Maybe we found a page that takes 10 seconds. Well, let's look into why. What's taking so long on that page? Is it a method that's very expensive? Is it a fetch to the database? Is it a multiple fetch to the database? Well, event display, after you find out what pages are giving you trouble, can tell you what methods on that page are giving you trouble. Who's taking the most time in your application? You can evaluate, and you should evaluate the times relative to the total time that your application is running.
Pay attention to what is used the most. If you have a page that's hit once a day in the corner and it's really expensive and it takes 20 minutes, you might not want to dedicate resources to fixing that page. You may instead want to dedicate resources to making the front page faster. or the query that everyone does. Now I'm going to ask Chris Mine, senior consulting engineer with Apple Eye Services, to come up and give us a demo about using wall recording and playback.
Chris? So we need the guest one. Thanks. So we're going to skip the recording portion, because we did that earlier. But just to show you what that looks like as a result, use the whole recording path. And we created this-- As a result of going through our application in a typical usage fashion, we ended up with this recording. And inside here are a number of files. You can see the request and response is making up transactions.
So this only has eight transactions, or excuse me, nine transactions in it. And then we'll just look at one of the contents of one of these. You may be able to tell from that that it's a typical HTTP request. It's just recording the request that came in from the browser that the app got, and then afterwards it records the response it sends out. So that should look a lot like an HTML page.
So then I've already got this application running. Actually, it's this one here. And then from a command line, I'm just going to start up our playback where it's going to read from that directory, send each of the requests to the application, and then compare the result with what it gets back this time.
So we can see up here that it's going along doing some logging to show you that it's actually hitting the app. In this case, we only have one playback going, and we have only one application instance. And down here, you can see some of the results and the information that's coming back for us. So you can see it's got perhaps the requests.
It'll go through zero through eight, and then it starts over. So the next one will be a new session. So each time it gets to the initial request again, that's going to the main page again, and that's starting a new session. And then you can see the transaction times.
Let me stop that for a second. Over here, this is the comparison of the bytes that it expected to get versus the bytes that it actually did get. So that can, in some respects, you can tell if there's a real problem with your app, maybe a defect of sorts. If it's returning back something wildly different, that might well be an error page, something dramatically different than what it expected. So let me start that running again and go have a look at the... well stats information.
So we got the login page as mentioned before. By default, this will come up. If you haven't set up your app to allow you to log in here, you can't. There isn't a command line option for that. It's kind of a security feature. And there's a lot of information in this page. We're only interested today in part of it.
So I'll just go down to that bit. This happens to be, you can see here, there's two major sections. There's component action statistics and the direct actions. As you can see, there are no component actions, and that's because this app relies exclusively on direct actions. We see here on the left the page names. It doesn't give you anything that's at a subpage level in this view.
And then I'd like to point out these minimum and maximum times over here. You can see that it's not terribly obvious on this hardware because it's ripping fast, but on my laptop it showed a dramatically different value for the max and for the min. I mean, this one's still pretty large, but it just goes to show that maybe on the first hit, first time you hit the main direct action, the default direct action, it might take a lot longer for something to happen, but then over time, this average is going to go lower and lower. And you can see there the 0.01 versus the half a second.
So another thing we should be able to see in here, what we just saw there is that our session timeout is very short. I think we set it to about 15 seconds just to demonstrate this other feature. which is the current active sessions versus the maximum. So here we have 163 sessions created.
But our current active session is only 68. It's likely to stay at 68 for a long time. It might fluctuate, go up or down one. But it shows this app's essentially being driven at a steady state. Which is essentially what we want, because otherwise information we get about CPU usage and information we get about RAM usage would be pretty much irrelevant. So now go on to the WoEvent display. So here's the event logging that Fitz referred to. We're just going to look at component events.
Then once you set up what events you're interested in, you can actually display those events. In this case, we're going to just reset this. To get a fresh view. So our playback is still running, so this information is still getting updated. And you can see that for the component events, there's the pages, but it's also the subpage components as well. There's different sorting capabilities to show these events in different ways. So we'll group them by page.
Then we can see something like Oh, perhaps that the add movie page takes what seems like an awful long time for the number of calls relative to anything else. So if you were going to get some sort of speed increase, you might look for something in here on an app-wide basis. Something else that's worth noting is that if these times per call are below what your criteria is, then you're pretty much done. So this just tells you you don't have any performance tuning to do, which is pretty much the case in this application.
But you can drill down in here and see where the cost of rendering a component comes from. So at the bottom of things we see the WD footer costs pretty much nothing. So maybe I had to turn my attention to the WD table element if I were going to look for a place to pick up some speed.
Okay, and that's it. Now we're going to, and now we've talked about ways of measuring your performance of your application, we're going to talk about techniques for improving the speed of your application, techniques specific to WebObjects and the Enterprise Object Framework. Now, maybe we could review this week.
We talked about there were two enterprise object adapters available. There were the JDBC adapter and the-- anyone else remember? The NUN adapter. Now, the NUN adapter is extremely fast. There are absolutely zero round trips to the database. And however, it's not quite as useful as the JDBC adapter. We want to reduce trips to the database.
EOF writes, as I said before, the SQL and sends it to the database, and the database sends back, well, data. Sometimes, EOF will ask for the database for data in a not so intelligent manner, we might say. It might repeatedly ask the database for little bits of data that it could have just asked for all at once. So to find out where these problems exist, we turn on the user default, EOAdapterDebugEnable.
Now for those of you who use print statements in your application to see what some value of something is, where some sort of value changes, or where you are currently in the request response loop, you'll see a number of print statements go by on your console. When you turn on EO Adapt or Debug Enabled, you are going to see more stuff on your console than you ever knew what to do with. You're going to see when EO F first logs into your database. You're going to see every round trip thereafter. tons and tons and tons of fun things to look through.
Now, when you look at this information that UF tells you, perhaps you'll see that there's multiple round trips for your database for one request response loop. Maybe you're fetching, for our example, those of you who are familiar with the movies database, I'm not creative enough to come up with election results and stuff like that. So I grab the movie database. It ships. It's in the Java business logic examples. It's tried and it's true.
When I fetch movies, and then perhaps I navigate some of these faults one at a time, as each fault fires, it might make a separate trip to the database. And it's extremely inefficient because we all know that each round trip to the database is pretty expensive. So we're going to talk about ways that you can optimize that.
One is to use batch faulting. Tell EOF that when you fire off one fault, go get a whole bunch more. We're going to use prefetching. Tell EOF, when you fetch these entities, I want you to fetch these entities. relationships at the same time just because we're going to be using it pretty soon.
We could use a shared editing context. This was a recent addition to WebObjects last year. Fantastic. People were already doing this, putting an editing context in their application and talking to it. This is a really intelligent editing context that has relations to each editing context in the session. You could put reference data into the shared editing context, fetch it when your application starts up, and never fetch it again. Or, well, there's a little detail on that I won't go into right now, but it will refresh your data eventually.
So if you have a WoW pop-up containing all the cities that your company may service, or a WoW pop-up containing something like all the products in your product line, and something that doesn't change or changes very infrequently, it might be a great candidate for shared editing context. Make your app read-only. This is extremely fast. If you have an application, a scenario, for example, where you have a back-office application where users enter data, where they change data, where they delete data, and then a front-end application.
You can actually go ahead and put everything in the shared editing context. And your application will rarely have to go back to the database for anything. Not only is it extremely fast, but it's extremely memory efficient because you don't have all these duplicate copies. Remember, each editing context that contains an object has another copy of that object. For very good reason, mind you. But when there's situations where you can avoid that, you certainly would want to. Last, you want to avoid heraldry. You want to avoid recurring fetches of reference data. Certainly, the shared editing context takes care of that for us.
Another thing we can do is reduce the amount of data that we get from the database. You can use raw rows for non-object data and very large fetches. Raw rows are, as you'll see, they're a big speed improvement on just a standard fetch, because they fetch the information into EOF just as always.
The information is fetched in the database, columns and rows, put into a dictionary, but instead of taking the next step and creating your enterprise objects, objects with the business logic built into them, you just get this raw data. Just straight attributes. There's occasions where it's very convenient to have that.
Use fetch limits or don't allow unrestricted searches. Now, I have to say right now that my favorite search engine out there is Google. You know, you go to that, I find obscure error messages, I type the error message into Google, and it comes back with a page with the answer on it, you know? I mean, who, anybody else does this? Anyone else? Cough or something, you know? Is anybody still awake out there? My goodness.
Shouldn't have eaten those big lunches. It only gives you, you can search for the word "apple" and you'll get hundreds of thousands of responses. It'll say, "Found four bajillion pages." And it's only going to show you 200. Encourages you, as a user, to refine your search. Maybe you want to look for "apple computer." "Apple computer G4." "Apple computer G4 with a cinema display." Maybe I'll optimize your programming experience. Um... Don't allow unrestricted searches.
Make the user enter some sort of criteria. Now this isn't always necessary. If you have a company with 50 employees and they have search on last name, search on first name, sure, let them hit return, let them get all 50 employees and surf around to the heart's content. If you work for a big company that has 200,000 employees, this might not work so well.
An unrestricted search might allow them to fetch your whole database, at which point your DBA is going to come hunt you down and kill you. And then you're also going to run a memory in your machine. So now, I'm going to go ahead and give you a little demo of some of the things that we did here. So, let's see. We should have two different screens coming up shortly.
Now those of you who have been to the WebObjects sessions this week, you guys have probably seen some really cool demos, some really cool graphics. You saw XM Elementary, you know, we got movie mechanics. You know, I have to confess, when I finished writing this demo yesterday at about 11.30, it looked like, you know, W3C Consortium around like 1994, you know, gray background with the big HRs across. We handed all this stuff over to Matt Furlick. A lot of you know Matt Furlick, who really made this stuff look great. Let's hear it for Matt.
Okay, so what I have here is an application called Moving Mechanics, which I've already cranked up and done some fun stuff with. So I'm going to stop it for starters. And, yeah, I didn't even mean that. And now we're going to start the application again. And when it decides to start up here, there it goes. We have a couple of different defaults that I've set up in here. In fact, I could probably still go ahead and take a look at this thing here. Let's see. Optimization. Did I click that right? Optimization targets. Yeah.
Anyone? Resize? Oh, it's way down here hiding, right? There we go. Okay, you'll see I have EO Adaptive Debug enabled to yes. I've got it set to WOL port 1729 because I'm hitting it from this machine over here. And then WOL auto open in browser. No, I'm not a big fan of the auto open in browser thing. So let's go back to where we were.
So the application is launched here and you'll see it says welcome application. It gives you this URL and some other information. And the first thing it tells you here is connecting with dictionary. Okay? It's connected to our database. It began an internal transaction, fetched five rows. Okay? Now the first example we're going to do here is our shared editing context. Ah ha. See it showed me. Shared editing context example.
Okay? We're going to take a shared editing context using objects from a shared editing context versus a shared editing context. Versus using objects that we're just going to straight fetch from the database. So as you see we have those five objects. So when I click on this, alright, you notice up here, no change.
[Transcript missing]
Let's talk about what these do. The shared editing context. Fetch is the data at the beginning of your application when your application starts. No round trip to the database. It's way fast. And it's great for reference data. The regular fetch is just a plain old fetch and it requires, at the least, a round trip to the database. And as you can see, there's a lot faster. Shared editing context, again, this is basically following a reference. And the regular fetch talks to the database. So now we'll move on to something a little more interesting, batch fetching and pre-faulting.
So let me see, can I see a clap or make a noise? Who's used the batch fetching and pre-faulting here? That's faulting and prefetching. Okay, so we're going to have some people who already know the answer to this, right? So we have three ways of getting this data. We do a regular fetch.
Now, the way this component works is I'm fetching all movies out of the movies database, all 88 of them, and then I'm iterating through the movies and I'm touching the roles relationship. Each movie has a role. Indiana Jones, for example, has Harrison Ford, has the role of Indiana Jones and the role of Marion and whatnot. I'm triggering that fault. For each movie. So iterating, triggering those faults.
Now, we have a regular fetch, which it just goes through, does the fetch and then triggers those faults. We have batch faulting on, which sets the batch faulting on the roles relationship to 25. So what that tells EOF is that when you fetch these bunch of movies, And you navigate to this particular relationship, go ahead and get some extra stuff while you're there.
You know, you run into the grocery to pick up milk, pick up some butter, you know, is it a stick of butter, a loaf of milk, a loaf of bread and a gallon of milk? Pick up some other stuff while you're there because it's likely we're going to need this. And last but not least, pre-fetching, which is a very particular thing, which says when we fetch the movies, I want you to pre-fetch these certain attributes of the movies while you're there.
Okay? So, we have these three things. Regular fetch, batch faulting, pre-fetching. Who thinks the regular fetch is going to be fastest? Anyone? Thanks, Mark. Who thinks batch faulting is going to be the fastest? Come on, it's a little racier. Anyone think batch faulting? You all know that pre-fetching is at the bottom, so it's the fastest, right? All right, who thinks pre-fetch is going to be the fastest? All right, all right. Okay, so let's see what's going to happen here.
Regular fetch. Look at that. Oh, my God, would you look at that. Every one of those big lines that begin, from begin internal transaction, to commit internal transaction. It's a round trip to the database. Now, man, I can't even, that was a lot of round trips to the database. Look at that. 88 to be precise. Let's find out how long that took. That took 7.04 seconds. Now mind you, on my laptop this takes like 20 seconds. Batch faulting. Look at that. Look at that.
Now, we saw a good number of transactions go by here, but they're a little different looking. Right there. You see here, this is some information I put in here. Whoa, whoa, it's still going. What's it doing now? Hello. Someone telling me to do this machine? Anyway, let's see what that comes out to.
Okay. So anyway, here we have this transaction here that fetches... In this case, 84 rows. It fetches, it has a big or select statement. Select the role where the movie ID is this or this or this or this. So it comes back with a whole bunch more of these relationships on the first shot. Pre-fetching. Boom.
Right back at you. How many fetches was that? Two. Now look at the difference from that. A regular fetch where you're faulting, you're doing multiple round trips to the database. Many, many, many round trips to the database. Seven seconds here. 2.23 seconds for batch faulting and 0.5 seconds for batch faulting.
52 seconds for pre-fetching. Now, here's something that's interesting about this. Some people out there might be saying, well, geez, of course the first one's slowest because then you fetched it all in. In sleep, in this component, I'm invalidating all objects in my editing context, which pretty much wipes everything clean and makes the EOF do everything all over again. So we'll try this again just to show you that there's nothing funny going on here. It's actually going to the database again each time. It's going to be a little faster because certain things are cached and happy. and what not.
So, batch fetching, 2.40 seconds this time, and last but not least, the prefetching, 39 seconds, 0.39 seconds. Let's talk a little more about them. Regular fetch, one round tip to the database for each fault fired. It can be extremely inefficient, very, very time consuming. Batch faulting, it's defining the relationship in question.
Now, this is the difference between batch faulting and prefetching. Batch faulting, you can define that programmatically or in EOModeler per relationship. You define it once in EOModeler, and your applications use your model, and they just go their merry way, and it just happens. To many relationships only, you can't do batch faulting on a 2-1 relationship.
This fetches, again, the next n objects whenever a fault is fired. So, for example, in this demo, we set batch faulting across the roles relationship to 25. So, it fetches one, it faults in one relationship, and it gets the next 25. Last is prefetching. It's used on a fetch specification, so you have to consciously go in when you create a new fetch and say, I want you to prefetch these things. When you get the movie, I want you to prefetch the director, and I want you to prefetch the roles. Or, and you can prefetch multiple relationships.
It adds only one round trip to your original fetch and it's very, very fast. So enough of that. We'll move on to probably another one of my favorites, raw rows. Now, raw rows and objects. I just kind of blew it. I hit the button already. So what I'm selecting here are all the movie rolls in the database. I wanted something with more data. Obviously, I don't have that much data in this database. The difference is seemingly small when you have a small amount of data, but when you have a lot of data, the difference is really, really big.
So, I just fetched the raw rows, inadvertently, and let's take a look and see what the time was on that. .08 seconds. Now, I'm going to fetch objects. Now, raw rows fetches rows out of the database, every column in the row. In this case, I'm fetching every column in the row. You can write SQL and only fetch certain columns, if you wish. You can fetch the primary key and maybe the title of something. But in this case, I've fetched everything. It gave me back a bunch of dictionaries.
Objects, I fetched the information from the database. You get all attributes for each object, and they're wrapped in Enterprise Objects, and boom, you have your Enterprise Objects. So, let's see how fast that goes. .13 seconds. Now, we'll click it a couple times here to sort of let things normalize out and see what the difference is.
The difference, it depends. You'll see different speeds, but in the most part, it tends to come up. .3 seconds. So, about 50% or so of speed improvement. If you tweak out your raw row fetch, you can see a really, really huge improvement. Now, there is a fantastic, fantastic article that I used for reference on this on Stepwise written by Malcolm Crawford, and I think he's sitting right there, that tells you in great, great detail. Thank you, Malcolm. Yeah, there you go.
There you go. Malcolm, as we all know, has done a number of good services this week. Let's talk a little more... Let's talk a little more about what these do. Raw rows. Raw rows don't instantiate enterprise objects. You just get back dictionaries. Key value. Title, Indiana Jones. Director, George Lucas.
It was Lucas, wasn't it? Music, John Williams. OK. It's very fast. Doesn't have to select all columns. Objects is just a plain old fetch. It creates objects for all the EOs it fetches. And it can be slow for very large fetches, especially when you're dealing with tens of thousands of objects.
I'm not going to take a look at WoEvents setup right now, but I will really quickly see if I can hit on WoEstats and see what it tells me about this application of mine that I was running. Okay, so here we are. We have Be Nice and Share, which is my little shared editing context demo with an average time. That's basically pretty fast because even the non-shared part is only fetching a little bit of data.
There's the venerable main. RoundTrips. RoundTrips is where we did the different kinds of fetching, batch faulting, and prefetching. Look at that. The max is 7.2. You see that? That was the roundtrip where we went and batched and we single faulted everything in. And last but not least, RawRows, which was basically pretty fast because again, we're just fetching the object.
A single fetch, as you can see, we fetched 450 rolls. Still, that was very fast. Let me just kick something over here. And so it was very fast because we just fetched. I did one roundtrip to the database. So that's about it. Let's get back to the slides now. Thank you.
Okay. You know, I had to get a Star Trek joke in, but I'm not a big Star Trek Voyager fan, you know, so I went with the classic, right? Lazy instantiation. I took a programming WebObjects 2 class at Apple. In fact, I took programming WebObjects 1. But programming WebObjects 2 was taught by Kai Christensen. It was an absolutely fantastic teacher. Anyone had Kai for a teacher? Thank you.
It was the greatest class. I didn't fall asleep once. Kai said something to me that just absolutely killed me. He said, when you're programming, he says, you want to cook your steak until it's smoking and then 10 seconds less. And I said, Kai, what the hell does that mean? He said, you want to wait to the absolute last second to do something. If you don't have to do it, don't do it because it may turn out, you know, 20 feet away from here. You don't have to do it.
Use lazy instantiation. This is an example here. We have Shatner's ego, which is going to take a long time to get because it's really big, as we all know. So the first time it's called, it checks this private instance variable to say, is this null? If it's null, it goes ahead, fetches it, squirrels it away in the instance variable for the next time someone comes through, and then returns it.
Huge bonus. It's a great little trick to use. Back to EOF stuff. Subclass EO generic records. Instead of EO custom object. EO generic record has a couple of little tweaks to the key value code and the way it stores the information for the attributes of your EO. It's blisteringly fast, especially in WebObjects 5. It's absolutely incredible what they've done with this.
Don't overwrite. Now, another thing that Eogeneric Record does as well is, by default, deferred faulting is enabled. Now, let's say we fetch a movie, and it has several attributes. It has role, it has a link to director. What we used to do in the old days, back in the 1990s, we used to create a fault for each one of these attributes. Now, if a movie, let's say it has 10 possible faults, and we fetch 1,000 movies, okay? So, we've just had to create 1,000 movie objects plus 10,000 fault objects. Now, the Eogeneric Record, by default, creates one shared fault for all these different faults.
That's an incredible speed-up because you don't have to create all these additional objects, even though the fault objects were small. Next, and this one seemed a little bit obvious to you, but I've seen some people do some weird stuff. Don't override value for key. Key value coding, one of my greatest friends in the WebObjects framework, patented key value coding, as we all know, and we know that software patents are evil as well. Don't override that.
Value for key and take value for key are called incredibly often in enterprise objects, in your woke components, and they're optimized to be as fast as possible, something you don't want to override. Certainly, if you're debugging and you want to put something in there, print statement, but make sure you rip that whole thing out when you're done.
[Transcript missing]
This is one of the things that really blew my mind when someone explained it to me. They said, when you synchronize, when you go through the request response loop, if you put a simple string in your page, it's going to print out the title of a movie. Or something, or you have your own subcomponent, let's say it's a custom subcomponent that takes in a name or something. We all know the request response loop, big fans of the request response loop, Awake.
Take values from request, invoke action, append to response, and sleep. Those three middle ones-- take values from request, invoke action, append to response-- there's a lot of stuff going on there. And one of the things that goes on in those three methods are variable synchronization from parent component to child component. What that means is that take values gets called, boom, synchronize. Pass the value into the child component, pull it back out. Do it again, invoke action, pass it in, pull it out. Do it again, append to response, pass it in, pull it out.
That's like six method calls there. If you have a subcomponent that you're using everywhere, say a button bar component that just has some buttons on it that you want to pull some information down from the parent for, say it's just a footer, and we'll talk about a way of even making that footer faster in a moment, and you just need a little bit of information from the page, you might want to say, look, WebObjects don't synchronize these variables for me, but instead I'm going to take care of it myself. And then in the appropriate method of the three above that I mentioned, you can pull or push out that value that you need. Now, if you want to talk even faster, we can talk about using stateless components.
Now, a stateless component, whenever a component's instantiated, you get a new component. You say, you know, page with name, boom, new component. You put a component into a page, when that page gets instantiated, that subcomponent, boom, gets instantiated. If you say that component's stateless, WebObjects knows that this is a component that's not going to change based on a session or based on something a user's done.
It's a stateless component. It loads it up once and it reuses it over and over and over again. So, if you have 700 users in your website and you have that little footer that has the big legal disclaimer in it, make that stateless and you don't have 700 of those lying around. You have one. Not only is it faster, but you use less memory.
Now, in the end here, we've sort of thrown a couple little miscellaneous things, some of which aren't necessarily related to WebObjects. But, session timeout. Chris showed you before we had our session timeout set to 15 seconds. That's pretty extreme, but you might want to see what the typical user experience is in your application. When a session times out, that memory is available to the garbage collector to be picked up eventually. So, that means that when your session gets bigger, bigger, bigger, it can squish it back down a little more.
Fetch outside the request response loop. If a loser logs in, then you need to get some database on that user, but you don't need it right away in that first page. You might want to do a delayed action to fetch that or to put it in sleep or somewhere safe to put it outside that the user's not going to be waiting. Index your database. I'm a big fan of getting someone else to do this for me.
You can notice a marked improvement, especially if you're just using single attribute primary keys, which is what we all know and love with the Enterprise Objects Framework. Offload images and static data to another server. If you've got an application that's serving up, I just experienced this recently, QuickTime VRs, 300, 400 kilobyte VRs, and you've got 200,000 of these things.
Well, you don't want your same web server that's serving up your web app to be serving up these images. You might want to have an additional web server, vrs.yourcompany.com. images.yourcompany.com. Have that serve up the static images and VRs and sound files and God knows what. Check your HTML.
Check the HTML. This is, wow, this is like really obvious to me now, but wasn't obvious to me at the time. I had a page that we designed for this client of ours with all these nested tables and it looked just the way they wanted it and I was like, oh, thank God that's over with. And then we hit the page and it just took forever to render. It took forever. This was back in like years ago, 1999, I think.
Check the HTML. See if you can't optimize your HTML. See if it's actually compliant. Run HTML, run a linting program over it. Make sure it's valid HTML. Remove extraneous tables. Do as much as you can with as little as you can. And last but not least, there's Java, there's Java performance tuning things like OptimizeIt or JProbe. I believe OptimizeIt is currently available on Mac OS X.
Now lastly, but not least, we'll talk a little bit about what we've learned. We talked about determining where the bottlenecks lie. Measure, modify, measure. Remember, again, in a controlled environment, change is only one thing. We talked about WebObjects and EOF techniques. We have batch faulting, prefetching. We have synchronizing bindings, not synchronizing bindings, stateless components, that sort of thing.
General programming techniques, lazy instantiation. Upgrade your hardware. Huge thing, right here. If you're running your application and you're hitting swap in a deployment environment, you're doomed. There's nothing that you can sit down and optimize and improve that, the second that your machines run out of RAM. And oftentimes, you'll discover that hardware is a lot cheaper than developer manpower.
So with that, I want to let you know that if you hurry up, please don't run. The WebObjects lab is open for another 20 minutes downstairs in room K. The only session left for WebObjects Track is the feedback forum, which is going to start at 3:30. And there's your contact information, which I'm sure is all new to you.