Caching

Retrieving information from a database takes time. With careful optimization, you can reduce the time and lessen the burden imposed on the server to a certain extent, but you can never eliminate it. Caching is an elegant solution to this problem. With a system that uses caching, some data requests won't require a database connection and a query. Instead, they'll retrieve the information directly from server memory a much faster proposition.

Of course, storing information in memory isn't always a good idea. Server memory is a limited resource; if you try to store too much, some of that information will be paged to disk, potentially slowing down the entire system. That's why the best caching strategies are self-limiting. When you store information in a cache, you expect to find it there on a future request, most of the time. However, the lifetime of that information is at the discretion of the server. If the cache becomes full or other applications consume a large amount of memory, information will be selectively evicted from the cache, ensuring that performance is maintained. It's this self-sufficiency that makes caching so powerful (and so complicated to implement on your own).

With .NET programming, you can take advantage of a powerful caching system, provided you use XML Web services. That's because caching is built into the ASP.NET platform. It isn't available to components that use .NET Remoting. In fact, caching is so valuable that you might want to implement a data-intensive part of your application using XML Web services rather than .NET Remoting, even though the cost of SOAP communication will be higher.

ASP.NET provides two types of caching. Output caching works declaratively through the <WebMethod> attribute and reuses an entire response. Data caching needs to be coded programmatically, but it enables you to implement more flexible caching strategies. Microsoft's case studies generally favor output caching because of its simplicity, but there's no reason that you can't combine both, even in a single XML Web service.

Output (Response) Caching

Output caching stores the result of a Web method invocation and reuses it for other clients for a set amount of time. Output caching is coarser than data caching because it can be used only on a per-method basis. You enable output caching by setting the CacheDuration property of the <WebMethod> attribute.

Listing 12-8 shows a simple example of output caching. It caches the result of the GetDate method for 3 minutes (180 seconds).

Listing 12-8 Enabling output caching
 <WebMethod(CacheDuration := 180)> _ Public Function GetDate() As String     Return System.DateTime.Now.ToString() End Function 

You can test this Web method using the Internet Explorer test page. The first time you call the method, you'll receive the current time. If you call the method a second time within the allotted 3 minutes, you'll receive the same result. In fact, ASP.NET bypasses the code completely and just returns the result stored inside its cache. (You can test this by setting a breakpoint in the GetDate Web method. When ASP.NET reuses a result from the cache, your code won't run and the breakpoint won't be triggered.)

In fact, any client who makes a request in the 3-minute time period receives the exact same result. After the time limit has expired, the result is removed from the cache. On the next request, ASP.NET runs the GetDate function, adds the result to the cache, and reuses the new result for another 3 minutes.

Things get a little more interesting if you create a Web method that accepts parameters. In this case, ASP.NET reuses a request only if every parameter matches exactly. Consider, for example, the GetStockQuote method shown in Listing 12-9.

Listing 12-9 Output caching with parameters
 <WebMethod(CacheDuration := 60)> _ Public Function GetStockQuote(ticker As String) As Decimal     ' (Lookup code omitted.) End Function 

In this case, every time ASP.NET receives a request with a new ticker parameter, it caches a separate result. If it receives a request for three different stocks, it caches three different stock quotes and reuses each one for 60 seconds from the time that it was placed in the cache. (To test this, create a modified version of the GetDate method that accepts a string parameter. Then try invoking the method with different parameters.)

Considerations for Output Caching

Output caching isn't suitable for every method. In fact, caching is always a compromise. If you cache too little for too short a time, your code will need to query the database frequently. On the other hand, if you cache too much, you'll risk forcing important information out of the cache prematurely. Generally, you should try to apply output caching to methods with the following characteristics:

  • They take time or encapsulate a limited resource.

    If a method just wraps some trivial code that never hits a database, you can't realize much savings by adding output caching.

  • They depend only on their parameters.

    For example, the GetDate method isn't a perfect caching candidate because it depends on the computer clock. When a request is reused, ASP.NET can't take into account that the clock has changed, so the response is inaccurate. Similarly, if you have a Web method that reads user-specific information from a cookie or ASP.NET's session state, it isn't a good caching choice.

  • They have a limited set of possible input parameters.

    This is the single most important factor with output caching. It doesn't make sense to cache a method that accepts a wide range of parameters because the cache will fill up with a separate response message for each distinct request. Instead, you should cache methods that take only a few possible values.

  • They don't perform other tasks.

    In other words, if you have logging code included in your Web method, this code won't run when the cached response is reused. If you need to get around this limitation, you can use data caching to reuse cached data but still execute the method and some code.

Although these are all solid guidelines, making hard decisions about caching can actually be as much an art as a science. Consider this sample Web method:

 <WebMethod> _ Public Sub GetProductDetails(ID As Integer) As ProductDetails     ' (Code omitted.) End Sub 

Does it make sense to add output caching to this method? The answer depends on the specific usage patterns of the system. If you have a small number of products in the database and this method is used frequently, this might be an ideal scenario for data caching because you could save numerous trips to the database. On the other hand, if this method is part of the back end of a high-volume e-commerce site, you'll probably have far too many different products to cache them efficiently, and more cached responses will expire without being used.

Note

You should use performance counters, as described in Chapter 14, to study the effectiveness of your caching strategies. The ASP.NET Applications Counter category includes performance counters such as Output Cache Hits, Output Cache Misses, and Output Cache Turnover Ratio, which are designed for this purpose. You also can profile data caching with counters such as Cache API Hits, Cache API Misses, Cache API Hit Ratio, and Cache API Turnover Rate.


Data Caching

Data caching enables you to store specific pieces of information in a Cache object and reuse them as needed. In many ways, data caching is more powerful than output caching. Unfortunately, it also requires you to intermingle your business code with some ASP.NET-specific caching code. That means that from a design point of view, it's not as elegant as the declarative model used with output caching.

One reason you might use data caching is if you have several methods that need to reuse the same information. Suppose, for example, that you have an XML Web service with two methods: GetStockQuote and GetStockQuoteDetails. The second method is the same as the first but provides extra information, such as the daily high and low for the stock, in a custom structure. You could apply output caching to both these methods and cache their results separately. On the other hand, you might want to cache a single piece of information in a data cache the StockDetails structure and use it in both methods. With this approach, you could potentially reduce the number of trips to the database by half.

Data caching works through the System.Web.Caching.Cache object. You can access the current instance of this object through the Context.Cache property of your XML Web service, assuming your XML Web service class inherits from the base System.Web.Services.WebService class. If your class doesn't inherit from WebService, you can still access the cache using a slightly longer name: HttpContext.Current.Cache. This uses the shared Current property of the System.Web.HttpContext class, which always returns the current context object. The Cache object is not limited to a single service; instead, the information it stores is global to your entire Web application.

To add an item to the cache, you can just assign it using a new key name:

 Context.Cache("key") = ObjectToCache 

This technique is rarely used, however, because it doesn't enable you to specify the expiration policy for the cached item. Instead, you'll more commonly use the overloaded Insert method. Here's the most commonly used version of the Insert method:

 Context.Cache.Insert(key, value, dependencies, _                      absoluteExpiration, slidingExpiration) 

This method takes the string key (which you'll use to index the cached object), the value (the object you want to place in the cache), and an absolute and sliding expiration policy (which enable you to specify how long the object will exist in the cache). Note that even though the Cache.Insert method provides parameters for both sliding expiration and absolute expiration, you can't set both of them at once. If you attempt to do so, you'll receive an ArgumentException.

The Cache object also provides other methods to clear the cache, retrieve all the cached items, and set file and object dependencies. For more information about these details, refer to the MSDN Reference.

Absolute Expiration

Absolute expiration invalidates cached items after a fixed period of time. It works like output caching, setting a "best before date" on the cached information. Absolute expiration is the best approach if you need to store information that changes frequently and that needs to be periodically refreshed. For example, you could use absolute expiration to keep a stock quote for 10 seconds or a product catalog for 1 hour.

Listing 12-10 shows an example of absolute expiration that stores an object (named ObjectToCache) for 10 minutes. The TimeSpan.Zero parameter is used simply to disable ASP.NET's other form of caching policy, sliding expiration.

Listing 12-10 Using absolute expiration
 Context.Cache.Insert("key", ObjectToCache, Nothing, _                      DateTime.Now.AddMinutes(10), TimeSpan.Zero) 
Sliding Expiration

Sliding expiration sets an idle timeout on an object. If the cached information is not reused within that amount of time, it's removed from the cache. However, every time it is used, the object's lifetime is reset. Sliding expiration works similarly to the lifetime lease policy with components over .NET Remoting. Sliding expiration works well when you have information that is always valid but is not always being used. One example is historical data. This information doesn't need to be refreshed, but it shouldn't be kept in the cache if it isn't being used.

To use sliding expiration, you need to set the absoluteExpiration parameter to DateTime.Max. You can then use the slidingExpiration parameter to indicate the number of minutes for the object's timeout. The example in Listing 12-11 stores an object until it has been idle for 10 minutes.

Listing 12-11 Using sliding expiration
 Context.Cache.Insert("key", ObjectToCache, Nothing, _                      DateTime.MaxValue, TimeSpan.FromMinutes(10)) 

Note that it's impossible to predict how long this object will be in the cache. The item could be invalidated in as few as 10 minutes or kept indefinitely if it is in continuous demand.

Retrieving a Cached Item

To retrieve a cached item, you just look it up using the string key you used to store it. Keep in mind that all objects are stored in the cache as generic System.Object types, so you need to cast the object to its real type.

 Dim MyObject As MyClass MyObject = CType(Cache("key"), MyClass) 

Never assume that an object exists in the cache. ASP.NET has complete freedom to remove the object at any time, ensuring optimum performance. If you try to retrieve an object that doesn't exist in the cache, you won't receive an error, just a null reference. Therefore, you must explicitly test the object before you attempt to use it (as shown in Listing 12-12).

Listing 12-12 Retrieving a cached item
 Dim MyObject As MyClass MyObject = CType(Cache("key"), MyClass) If MyObject Is Nothing Then     ' Object does not exist in the cache.     ' You must reconstruct it (for example, re-query the database). End If ' (Process MyObject here.) 

To consider how you might want to integrate data caching into your XML Web services, let's return to the example of a service that exposes both a GetStockQuote and a GetStockQuoteDetails method, which return similar information based on the cache. Here's the custom StockQuoteDetails structure:

 Public Class StockQuoteDetails     Public Name As String     Public CurrentValue As Decimal     Public DailyHigh As Decimal     Public DailyLow As Decimal End Class 

Listing 12-13 shows both Web methods. They rely on a private LookupStockQuote method, which checks the cache and performs the database lookup only if required.

Listing 12-13 Using data caching in an XML Web service
 <WebMethod()>  Public Function GetStockQuote(ticker As String) As Decimal     Dim Quote As StockQuoteDetails = LookupStockQuote(ticker)     Return Quote.CurrentValue End Function <WebMethod()> _ Public Function GetStockQuoteDetails(ticker As String) As StockDetails     Dim Quote As StockQuoteDetails = LookupStockQuote(ticker)     Return Quote End Function Private Function LookupStockQuote(ticker As String) _   As StockQuoteDetails     ' If the quote is in the cache, it will be indexed under     ' the ticker symbol.     Dim Quote As StockQuoteDetails     Quote = CType(Cache(ticker), StockQuoteDetails)     If Quote Is Nothing Then         ' Object does not exist in the cache.         ' It must be retrieved from the database         ' (using a custom database component).         Quote = DBComponent.GetStockQuote(ticker)         ' Once it's retrieved, it should be added to the cache         ' for future requests.         Cache.Insert(ticker, Quote, Nothing, _                      DateTime.Now.AddMinutes(15), TimeSpan.Zero)     End If     Return Quote End Function 

It's probably not practical to create an XML Web service with two such similar methods, but this is a good example of how you can separate your data caching code from your business logic. Both methods are entirely unaware of how the stock quote was retrieved and whether it required a database lookup.

More Information

To see data caching at work, see the case study in Chapter 18, which uses it to store session tickets and save time when authenticating Web method requests. Chapter 17 also puts caching to work to store pricing data.


Determining a Caching Strategy

Caching provides a simple way to achieve some staggering results. In the GetStockQuote example, you could save dozens of database calls every minute, vastly reducing the overhead of your application. In fact, caching is unique in the world of distributed programming because it improves both performance and scalability. Cached requests bypass your code and are returned much faster than normal, thereby improving performance. As you add more clients to the system, the number of trips to the database might not change very much, and therefore the overall burden on the system will start to level off (as shown in Figure 12-6), demonstrating admirable scalability.

Figure 12-6. The effect of good caching

graphics/f12dp06.jpg

Caching also represents a compromise that sacrifices complete accuracy and instantaneous updates. There is no easy (or scalable) way to make a cached object dependent on content in a database. This means that cached data is often stale data, which presents a snapshot of how the information looked several minutes in the past. In most applications, this is not a problem in fact, the need for immediate updates is often overstated. An e-commerce application, for example, can use caching without worrying that new product categories will appear an hour or two after the changes are committed. Of course, a system that provides volatile or time-sensitive data might not have this luxury. For example, many financial applications can't afford to cache a currency exchange rate.

Here are a few more tips for incorporating caching into your applications:

  • Microsoft recommends that you design distributed applications with caching in mind. Ideally, you should plan for caching in the design stage and identify its most useful niche.

  • Don't be afraid to cache for a long time. Microsoft's IBuySpy case studies, for example, store the output for some pages for 100 minutes. Stale data is generally a minor inconvenience compared to the performance improvements you'll see.

  • Don't worry about testing XML Web services that use output caching. ASP.NET automatically creates a dependency on the XML Web service assembly file for all items in the output caching. Every time you recompile the XML Web service, the cache is emptied, enabling you to execute your new code.

  • You can manually empty out the cache in a hurry in several ways. The easiest way is just to open the web.config file for the application and save it. When ASP.NET detects the file change, it automatically migrates the application to a new application domain, which won't contain the cached items.

Note

You can implement a crude form of caching by creating a singleton .NET Remoting component that stores some data and uses a sliding lifetime policy that keeps it alive only while it is being used. You could create a more sophisticated form of homegrown caching using a COM+ pooled component, as demonstrated in Chapter 9. However, neither of these approaches is as efficient or reliable as the built-in ASP.NET caching service. This is one of the main reasons you might favor using an XML Web service over a .NET Remoting component for high-volume use.




Microsoft. NET Distributed Applications(c) Integrating XML Web Services and. NET Remoting
MicrosoftВ® .NET Distributed Applications: Integrating XML Web Services and .NET Remoting (Pro-Developer)
ISBN: 0735619336
EAN: 2147483647
Year: 2005
Pages: 174

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net