Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Server-Side Caching in COM+ and VB : Page 5

Are there times when server-side caching is a good idea? How does server-side caching impact data access performance? Testing the various techniques seems to be the only way this can be determined. Thats what this article is all about. It examines and shows test results for COM+ techniques that are VB-friendly such as using the Shared Properties Manager, using the file system, and using global variables, a lookup table, the Global Interface Table, and commerce.dictionary, and compares these techniques to the caching performance of going to the database each time for data and the baseline when no data is read. Then the article looks at other algorithms and caching options you might want to consider. Also provided with the article is a tool you can use for your own tests.


advertisement

USING GLOBAL VARIABLES

A global variable in Visual Basic is a variable that is declared in the declarations section of a module. It doesn’t matter if the global variable is declared as public, private, dim, static, or global, or even if the static variable is in a function in a module.

Using global variables in COM+ components (built with Visual Basic) can be really dangerous if you don’t know how they will work in that environment. Each activity (logical thread) in COM+ will run on its own physical thread and each physical thread has its own instance of global data. When there are more activities than physical threads, the activities will be multiplexed over the physical threads. Therefore, you will see your value in global variables sporadically overwritten if you use them for more than read-only values. For a deeper discussion about this, read my article MTS (and COM+) and global variables.



When you know how global variables work in COM+ components, you can benefit from using them instead of seeing the unpredictable results as a problem. If you use them for read-only values, there is no danger. Use a Sub Main in your component to set the values. If you have checked Retain in Memory for the project properties of your component (and you definitely should), the Sub Main will only be called once for each thread for the lifetime of the COM+ application. (Watch out for doing too much in Sub Main so that you don’t hit a specific DCOM timeout. You may also introduce code that is harder to debug. A better approach would be to use lazy initialization so that the value will be cached the first time it’s requested.)

Since each thread has its own value, there is no contention whatsoever between the threads. But beware that you are using an implementation detail that may change in any version! (In the current version of COM+ you have approximately ten threads per CPU. In MTS you had 100 threads by default.) This is the code I use in the Sub Main procedure for my tests.

Option Explicit

Global gsngVat As Single

Global gstrProducts As String

Public Sub Main()

gsngVat = VatGet()

gstrProducts = ProductsGet()

End Sub

And this is the code I use to fetch the static data in this scenario.

Private Function StaticFetch() As Single

StaticFetch = gsngVat

End Function

The test for reading semi-static data is not applicable without a totally different approach. In the case of global variables, there are really several values and not just one single value since each STA will have its own global data. Somehow the update has to be propagated out to the global data in all the different threads. Therefore I will not show any results here from that test. I will discuss this further in the section called Other algorithms below.

Figure 14 Results for Global variables

Global variables

No of transactions (1 CPU)

CPU-usage (1 CPU)

No of transactions (2 CPUs)

CPU-usage (2 CPUs)

Static read

50

100%

64

99%

From the results shown in Figure 14, you can see that using global variables (2 CPUs) is 64 times faster than going to the database each time (1 CPU). When was the last time you got a wage increase so that the new wage became 64 times larger?

Please note that this information is VB-specific. If you use global variables in Delphi for example, they will by default be global for the whole process.

In my opinion global variables are great for read-only data. In the VB-case it’s very hard to use them effectively for semi-static data.

USING A LOOKUPTABLE

A lot of ASP-developers started to use the VBScript dictionary object found in scrrun.dll but they found it produced threading issues except when used at the page level. (See the section about Common Traps for more information.) To solve the problem the ASP-team developed a dictionary object called LookupTable. LookupTable has Any Apartment (or Both as it is called in MTS) as its Threading Model.

Microsoft does not officially support this component, but as I understand it, it has become very popular in the ASP-community lately. To download the component, go to msdn.microsoft.com/workshop/server/downloads/lkuptbl.asp. You will also get the source code for the component, which could be nice if you want to extend it to your specific needs.

Working with the LookupTable is not complex. After instantiation, you use the method LoadValues to read the values from a text-file. After that you can use Value(index) or LookupValue(key) to read the values. You can also call ReadLock and ReadUnlock so that a read won’t interfere with a LoadValues call. A nice feature that comes for free is the option of having the object update its values automatically after a specific number of seconds (sometimes called aging view). Most often that is real-time enough for your application’s semi-static data.

Where do you save the object reference to the LookupTable-object? In my tests I chose to save the object in the SPM, but in order to keep the SPM from influencing the test results, I am caching the object in a global variable. Observe that with this solution, you will not hit the SPM often at all and the problems that I talked about earlier are not an issue.

The update of the semi-static value will refresh the file and then do a new load to the LookupTable. Therefore the read can always assume that the cache is up-to-date. This is the code I use for a semi-static fetch when using a Lookup Table. You can see the results in Figure 15.

Private Function SemiStaticFetch() As String

With gobjLookup

.ReadLock

SemiStaticFetch = .LookupValue(KeyProducts)

.ReadUnlock

End With

End Function

Figure 15 Results for LookupTable

LookupTable

No of transactions (1 CPU)

CPU-usage (1 CPU)

No of transactions (2 CPUs)

CPU-usage (2 CPUs)

Static read

42

100%

55

98%

Semi-static read

79

100%

99

81%

I’m sure a lot of you will be happy when you see the results with the LookupTable. It works very well as you can see and the implementation of aging view makes it very usable in many scenarios.

USING THE GLOBAL INTERFACE TABLE

GIT stands for Global Interface Table and is a Win32 facility. You can store interface pointers in the GIT to make an object global for all threads in a COM+ application. The GIT will also automatically take care of inter-apartment marshaling if necessary. The GIT is not usable directly from Visual Basic, but Don Box (yes, he’s my hero too) has written a small DLL (vbgit.dll, Global Interface Table Helper) that makes it possible to use the GIT from VB. You can download it from www.develop.com/dbox/com/vbgit.

You can use the GIT for creating a singleton. The GIT is also handy for solving a problem with repeated unmarshaling of interface pointers for objects that use the FTM, but that is nothing you have to think about in Visual Basic.

This technique is a bit different from the others that I have tested in this article since it’s possible to store complete objects even if they have been built with Visual Basic. Therefore I create an extra class called CacheInGIT with a property get/let for the VAT and the products-string.

When an object is passed to the GIT, a long value (called a cookie) is returned and that value is the key for getting access to the object again. Observe that I have kind of a catch 22 here since that cookie must be cached so that all requests can find it. The solution I use here is to store that long value in the SPM, but also in a global variable. I am protecting the creation and registration of the object with a mutex so that I only get one instance in the GIT.

I tried to cache the whole object in a global variable after having fetched it from the GIT, but then I sometimes had stability problems. I received Error 8001010e. The application called an interface that was marshalled for a different thread. The problem didn’t occur all the time, but when it occurred, the COM+ Application had to be shut down. As I understand it (and I also discussed it with MS-friends), this technique should be possible to use and the throughput was about 15 times better than what I now show. Anyway, if it only works every second time, the result isn’t interesting to show.

(BTW, you will get the same error if you put a VB6-object in the SPM and try to use the objects from different threads, but in this case the problem is expected.) This is the code I use to create the object and store it in the GIT. I’m also saving a long value in the SPM so that I can get back to the object in the GIT.

Set obj = New CacheInGIT

obj.Products = ProductsGet

obj.VAT = VatGet

lngDw = githelplib.RegisterInterfaceInGlobal(obj)

spmProperty.Value = lngDw

Set obj = Nothing

In this code, I get a reference to the object from the GIT, use the object, and then destroy the reference.

Private Function StaticFetch() As Single

Dim obj As CacheInGIT

Set obj = githelplib.GetInterfaceFromGlobal(glngDw)

StaticFetch = obj.VAT

Set obj = Nothing

End Function

Another strange thing to note is that the first time a GIT test runs, there can be an enormous difference in the throughput between the different test clients. One can have 50,000 tx and the other four can have 130. If the test is executed again without a reboot, all the five clients will have something like 400 tx. I have decided to not show the first case in the results that you can see in Figure 16.

Figure 16 Results for GIT

GIT

No of transactions (1 CPU)

CPU-usage (1 CPU)

No of transactions (2 CPUs)

CPU-usage (2 CPUs)

Static read

0.7

100%

0.9

99%

Semi-static read

1.9

100%

2.4

98%

The reason for the comparatively low performance improvement here is probably the marshaling across apartments and also the blocking issues for the lookup itself. In my opinion the GIT is not interesting in scenarios similar to my simple tests. It is probably useful in other situations when the object won’t be as stressed as it was here, especially if the object has a complex structure.



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date