|
![]() ![]()
February 1999Caching SQL data in the IIS Application objectby Scott Mauvais As good developers, we do our best to make our code as data-driven as possible. Back in the hay days of client-server development, this was relatively easy. While we distracted the user with a splash screen, we'd open up our connections to the database, load all the static data, and build our global structures. If there were any danger that this data was only semi-static, we could easily fire off a background thread to periodically check for any new data—and rebuild the structures if needed. Now that we're in an n-tier world ruled by cruel, stateless Web servers, our old, tried and true techniques no longer work. This presents us with an unpleasant tradeoff. On the one hand, you can open an expensive database connection and fetch the exact same static data for every user that loads a page. Performance and scalability would suffer but your application would be very easy to maintain. Conversely, you can create an extremely fast site by using static HTML pages that hard-code your lookup values. Sure, your site will scale to millions of users—but you'll have a difficult time keeping your pages current. In this article, we're going to show you a middle ground where you can
cache your static data in ASP's Next, we're going to walk you through dynamically generating the same
combo box by pulling the values out of a database. We'll then take this a
step further and show you how to cache this data in IIS's
We've chosen to use the Northwind sample data that ships with both SQL Sever 7.0 and all versions of Microsoft Access. That way, you can cut and paste the sample code in this article into your own environment and begin working right away. Once you have our sample pages running, you can easily substitute your own databases and tables and start creating high-performance Web applications based on your own data. Creating a static HTML combo boxFigure A shows a very simple Web page called HardCode.htm. We've intentionally removed all of the extraneous design elements so that you aren't bogged down in the HTML and can focus on the logic behind creating the combo box. The page itself contains an HTMLform made up of three elements: a line of
text describing what we expect the user to do, the combo box of possible
choices, and a Submit button that posts the form's contents to another ASP
page for processing. As we're only interested in the combo box, we're only
going to discuss this element. If you want to see how the entire page is
constructed, go to www.infogenics.com/ZD/ViewHardCode.htm
where you can see the fully commented HTML code (choose View | Source to
see the source code).
Figure A: Our sample page shows a combo box containing the
Northwind confections. The code snippet in Listing A shows the HTML we used to render the combo box on this page. As you can see, it contains all of the confection items from the Products table in the Northwind database. Listing A: HTML used to build a combo box <SELECT name=cboProducts> <OPTION VALUE=48> Chocolade </OPTION> <OPTION VALUE=26> Gumbär Gummibärchen </OPTION> <OPTION VALUE=49> Maxilaku </OPTION> <OPTION VALUE=25> NuNuCa Nuß-Nougat-Creme </OPTION> <OPTION VALUE=16> Pavlova </OPTION> <OPTION VALUE=27> Schoggi Schokolade </OPTION> <OPTION VALUE=68> Scottish Longbreads </OPTION> <OPTION VALUE=20> Sir Rodney's Marmalade </OPTION> <OPTION VALUE=21> Sir Rodney's Scones </OPTION> <OPTION VALUE=62> Tarte au sucre </OPTION> <OPTION VALUE=19> Teatime Chocolate Biscuits</OPTION> <OPTION VALUE=50> Valkoinen suklaa </OPTION> <OPTION VALUE=47> Zaanse koeken </OPTION> </SELECT> When you create a combo box on a Web page, you delineate it by using
the HTML When the browser draws the page, it uses the text between
While it might initially seem preferable to receive the actual text
selected rather than some code, in most database applications you'll want
to get the primary key associated with a textual entry. That's exactly
what we've done here; we've used ProductID (the primary key of the
Products table in the Northwind database) as the You can see that creating pages using this approach is quite easy. All
you need to do is to create the HTML This approach works fine until someone in the merchandizing department decides to add a new product to the items available for sale on your site. Now you have to manually update (and retest) dozens of pages over several servers in a Web farm. Sure, you could automate this with SED- or AWK-like utilities (programmable text editors), but there's a better way to keep your Web pages and database synchronized. Filling the combo box dynamicallyAs you've probably surmised, a better way to populate a combo box is to have your Web page build the combo box dynamically based on the items currently in your Products table. By using this method, your Web pages will always contain the latest and greatest list of confections in your database.To dynamically populate the combo box, we created an ASP Web page
called Dynamic.asp that uses ADO to open a connection to the database,
issue a SQL To simplify the listing here, we've skipped declaring the variables and removed the error trapping you should include in a production application. You can find the complete code with more extensive comments at www.infogenics.com/ZD/ViewDynamic.htm. Listing B shows the first part of the ASP code where we open the ADO
object and request the data from the server. Here, we're creating ADO
Listing B: ASP code to retrieve rows using ADO '-- Instantiate ADO objects Set cnNorthwind = Server.CreateObject("ADODB.Connection") Set rsProducts = Server.CreateObject("ADODB.Recordset") '-- Open connection and recordset sSQL = "SELECT P.ProductID, P.ProductName " _ & "FROM Products P " _ & "WHERE P.CategoryID = 3 /* confections */ " _ & "ORDER BY ProductName" cnNorthwind.Open "Northwind", "scott", "secret" rsProducts.Open sSQL, cnNorthwind, _ adOpenForwardOnly, adLockReadOnly Now that we've created the objects, we can go ahead and open a
connection to the database. We do this by calling the To do this, we use Creating the HTML for the combo box and sending it to the browserOur application now has all the data and it's time to create the HTML for the combo box and send it to the browser. Listing C shows the required code. This section of code is the crux of what you need to know to dynamically create the combo box and, fortunately, it's relatively straightforward.First, notice that we're using another one of ASP's intrinsic objects,
the Listing C: Creating the combo box with ASP '-- Send the opening SELECT tag to the browser Response.Write "<SELECT name=cboProducts> " Do Until rsProducts.EOF sOption = "<OPTION VALUE=" _ & rsProducts("ProductID") & ">" _ & rsProducts("ProductName") _ & " </OPTION>" '-- Send the OPTION tag to the browser Response.Write sOption rsProducts.MoveNext Loop '-- Close the select tag Response.Write "</SELECT>" We start and end this code section just as we did our first example
containing static HTML: we use the HTML Once IIS has executed this script, it sends plain HTML back to the browser. In fact, the dynamically created HTML is identical to the static HTML we created in the first example. That's the good news. The bad news is that this method is significantly slower than straight HTML—about 20 times slower. In the next section, we'll show you how to have a dynamic combo box and gain some of that performance back. Caching a dynamic combo boxIn the previous topics, we explained two of ASP's built-in objects,Sever and
Response . In this section, we're going to show you the
Application object and demonstrate how you can use it
cache a dynamically-built combo box and dramatically improve your
performance.
Up until now, all of the variables and objects had what's called page-level scope—meaning that they existed only for the duration of the page. ASP has two other scoping levels: application and session. A session-level variable is private to an individual user and remains active throughout that user's visit. By using session-level variables, you can create statefull applications—thus allowing you to store a user's information on the server and access it from all of the pages in your application. We could cache our combo box here, but it would waste a lot of memory and we'd have to hit the database to create a new combo box for each user. An application-level variable is a single instance shared among all users and it remains in scope as long as the application is running. In terms of ASP, an application is defined as all of the pages in a directory and its subdirectories. An application starts the first time a user requests a page in the directory and ends when the Web site is shut down. In other words, any data we store in an application variable is available to all users and never has to be re-created. This is the perfect place to cache our combo box, but how? Using the Application objectTo use theApplication
object to cache the combo box, we first create a new page called
Cached.asp . On the page, we'll create a procedure, shown in
Listing D, that builds the HTML for the combo box and stores it as an
application variable. This code takes a similar approach to the one we
used to build the dynamic combo box. We've left out the code to open the
connection and retrieve the data because it's the same. As always, you can
see the full code by going to www.infogenics.com/ZD/ViewCached.htm.
In the last example, shown in Listing C, we created individual strings
for each part of the combo box and wrote them separately back to the
browser. In this example, we need to build a single string that contains
the entire combo box including the Listing D: Procedure to create an application-level combo
box Sub GetProductsConfections ' [Open ADO connection and retrieve rsProducts] '-- Start HTML Select tag sOption = "<SELECT name=cboProducts>" & Chr(13) '-- Loop through result set Do Until rsProducts.EOF sOption = sOption _ & " <OPTION VALUE=" _ & Cstr(rsProducts("ProductID")) & ">" _ & rsProducts("ProductName") _ & " </OPTION>" & Chr(13) rsProducts.MoveNext Loop '-- End HTML SELECT tag sOption = sOption & "</SELECT>" '-- Update Application variable Application.Lock Application("gsProductsConfections") = sOption Application.UnLock End Sub Once we've built the HTML for the combo box, we need to store it in the
We have now created a new, application-level variable and we're ready
to use it in our page, as shown in Listing E. There you have it; in just
four lines of code (seven if you count comments) we've placed the code
that retrieves our cached combo box. We do this by retrieving our
application variable Listing E: Retrieving the combo box from the Application
object '-- Make sure applicaiton variable is populated '-- If not, populate it. If Application("gsProductsConfections") = "" Then Call GetProductsConfections End If '-- Write the cached value Response.Write Application("gsProductsConfections") The best part of this technique is that it's linearly scaleable. Were you to dynamically build the combo box for each user, at some point the traffic would overwhelm your database server because every single user must query the database. By caching the data in IIS, you can support an unlimited number of users by continuing to add new servers to your Web farm, because those additional users have no impact on the database. Infinite scalability! Not quite. With every bit of good news there's bad news to follow. The cached combo box is indeed four times faster than building one dynamically for each user, but it's still not as fast as straight HTML because parsing and interpreting the ASP code requires quite a bit of overhead. In addition, the final problem we need to overcome is the same one that affects all caching strategies: coherence. Refreshing the cached combo boxAs we learned in our discussion of ASP's three scoping options, application-level variables last until the Web site shuts down—and supposedly, that shouldn't happen very often. Unless we have completely static data—and if this were the case, we wouldn't have to worry about creating a dynamic application in the first place—our cached combo box will quickly become stale and we'll lose coherence between it and the data in the underlying SQL table.To solve this problem, we need a means of refreshing the cache on
demand. To do this, we can create an ASP page called
With the variable cleared, the next person to hit our
Listing F: ASP code to clear the combo box from the
cache <% Application.Lock Application("gsProductsConfections") = "" Application.UnLock %> Using this approach requires that someone hit the
To avoid this hassle, you can create a trigger on the Products table
that will automatically call the Listing G shows how we roll GET /zd/ClearCache.asp HTTP/1.0Once we have the shell command we want to execute, we use the second SET line to build the
SQL statement to call the xp_cmdshell extended stored
procedure. We can then run this SQL statement by using the
EXEC command.
Listing G: Creating a trigger to automatically update the ASP
cache CREATE TRIGGER trgClearASPCache ON [Products] FOR INSERT, UPDATE, DELETE AS DECLARE @sCmd VARCHAR(64) DECLARE @sSQL VARCHAR(128) SET @sCmd = '"C:\Bin\HttpCmd.exe 127.0.0.1 C:\Bin\ClearCache.req"' SET @sSQL = 'master.dbo.xp_cmdshell ' + @sCmd + ', NO_OUTPUT' EXEC (@sSQL) Performance considerationsTo determine the relative performance of the three methods of building combo boxes we've covered, we wrote a script to simulate 20 concurrent users repeatedly loading our sample Web pages off of a single processor 333 MHz PII running only IIS. We also created a fourth page that was an exact copy ofHardCode.htm
but gave it a .asp extension. Here, we used this empty ASP file to test
the overhead associated with the ASP.DLL filter parsing the file.
To give a perspective on the magnitude of the differences, Figure B shows the Performance Monitor log captured while we ran these three tests. The first thing to note is that we extended the vertical axis to 200 rather than the default 100 to make it more readable. This script increases the load until the CPU reaches 100 percent utilization—thus marking the maximum throughput the server can sustain. Once we peg the CPU, we read the Total Requests Requests/sec counter of the Web Service object to plot the effective throughput. The black line represents the CPU utilization, which pegs at 100 percent immediately under the load of 20 constantly hitting the page. The solid while line represents the total requests serviced by the Web server—so the higher this line, the greater the number pages served (and the better your Web server's performance). Figure B: We used Performance Monitor to compare the performance
of static, dynamic, cached, and empty ASP pages. As expected, static HTML gave the best performance. Somewhat
unexpectedly, requiring the ASP engine to parse an empty Web page reduced
throughput by over 75 percent. Next, retrieving the cached combo box from
the ConclusionIn this article, we walked you through one of the most common dilemmas in n-tier development: designing dynamic, data-driven Web sites that scale well. We started by showing you the two extremes—fast and hard (almost impossible) to maintain versus dynamic but slow. We then showed you a technique where you achieve a four-fold increase in performance and limitless scalability by caching your data in IIS and having SQL Server automatically update that cache when the data becomes stale.
![]() |