Nscache
Tcl interface to AOLserver's caching API, by Rob Mayoff.
This module lives in AOLserver CVS[1]
AOLserver implements a C API for caching arbitrary data. This module provides a Tcl API on top of the C API. The module is only compatible with AOLserver 3.x using nsd8x, or AOLserver 4.x.
In AOLserver 4.5.1 there is a builtin ns_cache command with largely equivalent but not identical functionality. Most noteably only serverwide caches are supported. It is possible to run this module in AOLserver 4.5.1, in which case it will override the builtin command. Note, however, that in AOLserver 4.5.1 connection thread pools are not specific to a virtual server by default, so any -thread caches you create will be visible to all virtual servers using the same connection thread pool.
Cache Types
- Global Size-Limited Caches
- Global Time-Limited Caches
- Thread-Private Size-Limited Caches
Cache Commands
- ns_cache create cachename ?-size maxsize? ?-timeout timeout? ?-thread boolean | -serverwide boolean?
- ns_cache eval cachename key script
- ns_cache flush cachename key
- ns_cache get cachename key ?varname?
- ns_cache names cachename ?pattern?
- ns_cache set cachename key value
Cache Types Nscache supports three types of caches:
Global Size-Limited Cache
ns_cache create cachename -size maxsize ns_cache create cachename -size maxsize -serverwide 1
Entries in a cache of this type are accessible to all threads. Each cache has its own mutex that protects access to its entries.
Cache values are stored as counted strings, so arbitrary binary data can be cached. A global cache stores strings instead of Tcl objects to prevent race conditions that could lead to heap corruption.
The cache has a maximum size specified when the cache is created. The size of the cache is the sum of the sizes of all the values in the cache; keys do not count toward a cache's size. If inserting a value into the cache makes the cache's size exceed its maximum, then cache entries are evicted starting with the least-recently used entry until the size is below the maximum size (or until only the new value remains in the cache).
Caches created with "-serverwide" option are common to all virtual servers in the process, whereas caches created without this option are attached to the current virtual server.
Global Time-Limited Cache
ns_cache create cachename -timeout timeout ns_cache create cachename -timeout timeout -serverwide 1
Entries in a cache of this type are accessible to all threads. Each cache has its own SINGLE mutex that protects access to ALL its entries.
Cache values are stored as counted strings, as in a global size-limited cache.
The cache has a maximum entry lifetime, called its timeout, specified (in seconds) when the cache is created. Every timeout seconds, AOLserver flushes all cache entries that have not were not created or accessed in the last timeout seconds.
Caches created with "-serverwide" option are common to all virtual servers in the process, whereas caches created without this option are attached to the current virtual server. Note that non-serverwide caches are visible to ns_cache_stats, etc commands built into AOLserver on all virtual servers. Creating a same-name cache in multiple virtual servers will result in a warning in the server log but their contents will be separate. What will happen if expiration/size settings are different?
Thread-Private Size-Limited Cache
ns_cache create cachename -size maxsize -thread 1
Each thread in AOLserver automatically gets its own private cache named cachename. Since a thread-private cache is only accessed by one thread, access to it does not require a mutex. Entries in one thread's cache are not visible to any other thread.
Cache values are stored as Tcl objects. When a value is stored in the cache, nscache computes its string form and uses the length of the string as the size of the value.
The cache has a maximum size, like a global size-limited cache. However, because of the way the cache value sizes are computed, the actual memory usage of the cache values may be several times larger than maxsize.
Thread-private caches may offer higher performance if the cached values are complex objects such as lists or scripts, but require more storage than global caches.
Cache Commands
ns_cache create cachename ?-size maxsize? ?-timeout timeout? ?-thread thread?
This command creates a new cache named cachename. If thread is given and is true, then it is a thread-private cache. Otherwise it is a global cache. If maxsize is given, then it is a sized-based cache. Otherwise, if timeout is given, then it is a timeout-based cache. Otherwise, it is a timeout-based cache with an infinite timeout.
This command returns nothing if it is successful.
ns_cache eval cachename key script
This command atomically sets and gets a cache value. First, it looks up key in the cache named cachename. If it finds an entry, it returns the value of that entry. Otherwise, it executes script, stores the return value in the cache, and also returns that value.
Script may optionally use the return command to return its value. For example, this will store the value "2" in mycache, if mykey is not already present:
ns_cache eval mycache mykey { expr {1+1} }
This will also store the value "2" in mycache:
ns_cache eval mycache mykey { return [[expr {1+1}]] }
If script raises an error, or exits with break or continue, then ns_cache eval simply returns the same condition without modifying the cache.
ns_cache eval will not wait on another concurrent ns_cache eval while the script is being evaluated but ns_cache get will wait on a concurrent ns_cache eval.
This command is particularly useful for global caches because of its atomicity. Consider this definition:
proc get_thing {key} { ns_cache eval thing_cache $key { # some long, expensive database operation that # computes the value for $key } }
ns_cache flush cachename key
This command removes the entry for key from the cache named cachename. If the cache has no entry for key, then nothing happens.
For global caches, ns_cache flush interacts with ns_cache eval. Suppose thread 1 has called get_thing A and is executing the long operation to compute the value for A. Thread 2 calls get_thing A and starts waiting for thread 1 to finish. Thread 3 calls ns_cache flush thing_cache A. Thread 1 will continue executing the long operation, but thread 2 will also start the long operation. When thread 1 completes the long operation, ns_cache eval returns the (now stale) value it computed, but it does not store the value in the cache. When thread 2 completes the long operation, ns_cache eval stores the (fresh) value it computed in the cache and returns the fresh value.
ns_cache get cachename key ?varname?
This command looks up key in the specified cache. It operates differently depending on whether varname was given:
* If the varname is given and the key is not found, then: Raises error * If the varname is given and the key is found, then: set varname to value, and return 1 * If the varname is not given and the key is found, then: Returns value * If the varname is not given and the key is not found, then: Raises error
If some other thread is in ns_cache eval when ns_cache get is called for the same global cache and key, then ns_cache get waits for the other thread to finish.
ns_cache names cachename ?pattern?
This command returns a list of all keys currently in the specified cache or the keys matched with optional pattern. If the cache is thread-private, then the list only includes keys that are in the thread's private cache.
ns_cache set cachename key value
This command stores value for key in the specified cache. It is roughly equivalent to this script:
ns_cache flush cachename key ns_cache eval cachename key { return value }
However, the ns_cache set command operates atomically.
Configuration
To enable the module in your server, edit your server config file and add
ns_section ns/server/YourServerName ns_param nscache nscache.so