Table of Contents
Overview: What Coherence can do for you...
Defining a Data Grid
The Oracle Coherence In-Memory Data Grid is a data management system for application objects that are shared across multiple servers, require low response time, very high throughput, predictable scalability, continuous availability and information reliability. For clarity, each of these terms and claims is explained:
- A Data Grid is a system composed of multiple servers that work together to manage information and related operations - such as computations - in a distributed environment.
- An In-Memory Data Grid is a Data Grid that stores the information in memory in order to achieve very high performance, and uses redundancy - by keeping copies of that information synchronized across multiple servers - in order to ensure the resiliency of the system and the availability of the data in the event of server failure.
- The application objects are the actual components of the application that contain information that is shared across multiple servers and that must survive server failure in order for the application to be continuously available. These objects are typically built in an object-oriented language such as Java (e.g. POJOs), C++, C#, VB.NET or Ruby. Unlike a relational schema, application objects are often hierarchical in nature, and may contain information that is pulled from any database.
- The application objects must be shared across multiple servers because a middleware application (such as eBay and Amazon.com) is horizontally scaled by adding servers, with each server running an instance of that application. Since the application instance running on one server may read and write some of the same information that an application instance running on another server reads and writes, that information must be shared. The alternative is to always access that information from a shared resource, such as a database, which will lower performance by requiring both remote coordinated access and Object/Relational Mapping (ORM), and decrease scalability by making that shared resource a bottleneck.
- Because an application object is not relational, in order to retrieve it from a relational database the information must be mapped from a relational query into the object; this is known as Object/Relational Mapping (ORM). Examples include Java's EJB 3.0 and JPA, and ADO.NET. The same technology allows that object to be stored in a relational database by deconstructing the object (or changes to the object) into a series of SQL inserts, updates and deletes. Since a single object may be composed of information from many tables, the cost of accessing objects from a database using Object/Relational Mapping can be significant, both in terms of the load on the database and the latency of the data access.
- An In-Memory Data Grid achieves low response times for data access by keeping the information in-memory and in the application object form, and by sharing that information across multiple servers. In other words, applications may be able to access the information that they require without any network communication and without any data transformation step such as ORM. In cases where network communication is required, the Oracle Coherence avoids introducing a Single Point of Bottleneck (SPOB) by partitioning - spreading out - information across the grid, with each server being responsible for managing its own fair share of the total set of information.
- High throughput of information access and change is achieved through four different aspects of the In-Memory Data Grid. First, Oracle Coherence employs an extremely sophisticated clustering protocol that can achieve wire speed throughput of information on each server, allowing the aggregate flow of information to increase linearly with the number of servers. Second, by partitioning the information, as servers are added each one assumes responsibility for its fair share of the total set of information, thus load-balancing the data management responsibilities into smaller and smaller portions. Third, by combining the wire speed throughput and the partitioning with automatic knowledge of the location of information within the Data Grid, Oracle Coherence routes all read and write requests directly to the servers that manage the targeted information, resulting in true linear scalability of both read and write operations; in other words, high throughput of information access and change. Fourth, for queries, transactions and calculations, particularly those that operate against large sets of data, Oracle Coherence can route those operations to the servers that manage the target data and execute them in parallel.
- By using dynamic partitioning to eliminate bottlenecks and achieving predictably low latency regardless of the number of servers in the Data Grid, Oracle Coherence provides predictable scalability of applications. While certain applications can use Coherence to achieve linear scalability, that is largely determined by the nature of the application, and thus varies from application to application. More important is the ability of a customer to examine the nature of their application and to be able to predict how many servers will be required in order to achieve a certain level of scale, such as supporting a specified number of concurrent users on a system or completing a complex financial calculation within a certain number of minutes. One way that Coherence accomplishes this is by executing large-scale operations - such as queries, transactions and calculations - in parallel using all of the servers in the Data Grid.
- One of the ways that Coherence can eliminate bottlenecks is to queue up transactions that have occurred in memory, and asynchronously write the end result to a system of record, such as an Oracle database. This is particularly appropriate in systems that have extremely high rates of change due to the processing of many small transactions, particularly when only the end result needs to be made persistent. Coherence both (i) coalesces multiple changes to a single application object and (ii) batches multiple modified application objects into a single database transaction, meaning that a hundred different changes to each of a hundred different application objects could be persisted to a database in a single, large - and thus highly efficient - transaction. Application objects pending to be written are safeguarded from loss by being managed in a continuously available manner.
- Continuous availability is achieved by a combination of four capabilities. First, the clustering protocol used by Oracle Coherence can rapidly detect server failure and achieve consensus across all the surviving servers about the detected failure. Second, information is synchronously replicated across multiple servers, so no Single Point of Failure (SPOF) exists. Third, each server knows where the synchronous replicas of each piece of information are located, and automatically re-routes information access and change operations to those replicas. Fourth, Oracle Coherence ensures that each operation executes in a Once-and-Only-Once manner, so that operations that are being executed when a server fails do not accidentally corrupt information during failover.
- Failover is the process of switching over automatically to a redundant or standby computer server, system, or network upon the failure or abnormal termination of the previously active server, system, or network. Failover happens without human intervention and generally without warning. (As defined by Wikipedia: http://en.wikipedia.org/wiki/Failover)\\
- Information reliability is achieved through a combination of four capabilities. First, Oracle Coherence uses cluster consensus to achieve unambiguous ownership of information within the Data Grid; in other words, at all times exactly one server is responsible for managing the master copy of each piece of information in the Data Grid. Second, because that master copy is owned by a specific server, that server can order the operations that are occurring to that information and synchronize the results of those operations with other servers. Third, because the information is continuously available, these qualities of service exist even during and after the failure of a server. Fourth, by ensuring Once-and-Only-Once operations, no operations are lost or accidentally repeated when server failure does occur. The combination of these four capabilities results is the information within the Data Grid being reliable for use by transactional applications.
As a result of these capabilities, Oracle Coherence is ideally suited for use in computationally intensive, stateful middle-tier applications. Coherence is targeted to run in the application tier, and is often run in-process with the application itself, for example in an Application Server Cluster.
Start Here!
Cluster your objects and your data
Overview
Coherence is an essential ingredient for building reliable, high-scale clustered applications. The term clustering refers to the use of more than one server to run an application, usually for reliability and scalability purposes. Coherence provides all of the necessary capabilities for applications to achieve the maximum possible availability, reliability, scalability and performance. Virtually any clustered application will benefit from using Coherence.
One of the primary uses of Coherence is to cluster an application's objects and data. In the simplest sense, this means that all of the objects and data that an application delegates to Coherence are automatically available to and accessible by all servers in the application cluster, and none of those objects and none of that data will be lost in the event of server failure.
By clustering the application's objects and data, Coherence solves many of the difficult problems related to achieving availability, reliability, scalability, performance, serviceability and manageability of clustered applications.
Availability
Availability refers to the percentage of time that an application is operating. High Availability refers to achieving availability close to 100%. Coherence is used to achieve High Availability in several different ways:
Supporting redundancy in Java applications
Coherence makes it possible for an application to run on more than one server, which means that the servers are redundant. Using a load balancer, for example, an application running on redundant servers will be available as long as one server is still operating. Coherence enables redundancy by allowing an application to share, coordinate access to, update and receive modification events for critical runtime information across all of the redundant servers. Most applications cannot operate in a redundant server environment unless they are architected to run in such an environment; Coherence is a key enabler of such an architecture.
Enabling dynamic cluster membership
Coherence tracks exactly what servers are available at any given moment. When the application is started on an additional server, Coherence is instantly aware of that server coming online, and automatically joins it into the cluster. This allows redundancy (and thus availability) to be dynamically increased by adding servers.
Exposing knowledge of server failure
Coherence reliably detects most types of server failure in less than a second, and immediately fails over all of the responsibilities of the failed server without losing any data. As a result, server failure does not impact availability.
Part of an availability management is Mean Time To Recovery (MTTR), which is a measurement of how much time it takes for an unavailable application to become available. Since server failure is detected and handled in less than a second, and since redundancy means that the application is available even when that server goes down, the MTTR due to server failure is zero from the point of view of application availability, and typically sub-second from the point of view of a load-balancer re-routing an incoming request.
Eliminating other Single Points Of Failure (SPOFs)
Coherence provides insulation against failures in other infrastructure tiers. For example, Coherence write-behind caching and Coherence distributed parallel queries can insulate an application from a database failure; in fact, using these capabilities, two different Coherence customers have had database failure during operational hours, yet their production Coherence-based applications maintained their availability and their operational status.
Providing support for Disaster Recovery (DR) and Continuancy Planning
Coherence can even insulate against failure of an entire data center, by clustering across multiple data centers and failing over the responsibilities of an entire data center. Again, this capability has been proven in production, with a Coherence customer running a mission-critical real-time financial system surviving a complete data center outage.
Reliability
Reliability refers to the percentage of time that an application is able to process correctly. In other words, an application may be available, yet unreliable if it cannot correctly handle the application processing. An example that we use to illustrate high availability but low reliability is a mobile phone network: While most mobile phone networks have very high uptimes (referring to availability), dropped calls tend to be relatively common (referring to reliability).
Coherence is explicitly architected to achieve very high levels of reliability. For example, server failure does not impact "in flight" operations, since each operation is atomically protected from server failure, and will internally re-route to a secondary node based on a dynamic pre-planned recovery strategy. In other words, every operation has a backup plan ready to go!
Coherence is architected based on the assumption that failures are always about to occur. As a result, the algorithms employed by Coherence are carefully designed to assume that each step within a operation could fail due to a network, server, operating system, JVM or other resource outage. An example of how Coherence plans for these failures is the synchronous manner in which it maintains redundant copies of data; in other words, Coherence does not gamble with the application's data, and that ensures that the application will continue to work correctly, even during periods of server failure.
Scalability
Scalability refers to the ability of an application to predictably handle more load. An application exhibits linear scalability if the maximum amount of load that an application can sustain is directly proportional to the hardware resources that the application is running on. For example, if an application running on 2 servers can handle 2000 requests per second, then linear scalability would imply that 10 servers would handle 10000 requests per second.
Linear scalability is the goal of a scalable architecture, but it is difficult to achieve. The measurement of how well an application scales is called the scaling factor (SF). A scaling factor of 1.0 represents linear scalability, while a scaling factor of 0.0 represents no scalability. Coherence provides a number of capabilities designed to help applications achieve linear scalability.
When planning for extreme scale, the first thing to understand is that application scalability is limited by any necessary shared resource that does not exhibit linear scalability. The limiting element is referred to as a bottleneck, and in most applications, the bottleneck is the data source, such as a database or an EIS.
Coherence helps to solve the scalability problem by targeting obvious bottlenecks, and by completely eliminating bottlenecks whenever possible. It accomplishes this through a variety of capabilities, including:
Distributed Caching
Coherence uses a combination of replication, distribution, partitioning and invalidation to reliably maintain data in a cluster in such a way that regardless of which server is processing, the data that it obtains from Coherence is the same. In other words, Coherence provides a distributed shared memory implementation, also referred to as Single System Image (SSI) and Coherent Clustered Caching.
Any time that an application can obtain the data it needs from the application tier, it is eliminating the data source as the Single Point Of Bottleneck (SPOB).
Partitioning
Partitioning refers to the ability for Coherence to load-balance data storage, access and management across all of the servers in the cluster. For example, when using Coherence data partitioning, if there are four servers in a cluster then each will manage 25% of the data, and if another server is added, each server will dynamically adjust so that each of the five servers will manage 20% of the data, and this data load balancing will occur without any application interruption and without any lost data or operations. Similarly, if one of those five servers were to die, each of the remaining four servers would be managing 25% of the data, and this data load balancing will occur without any application interruption and without any lost data or operations – including the 20% of the data that was being managed on the failed server.
Coherence accomplishes failover without data loss by synchronously maintaining a configurable number of copies of the data within the cluster. Just as the data management responsibility is spread out over the cluster, so is the responsibility for backing up data, so in the previous example, each of the remaining four servers would have roughly 25% of the failed server's data backed up on it. This mesh architecture guarantees that on server failure, no particular remaining server is inundated with a massive amount of additional responsibility.
Coherence prevents loss of data even when multiple instances of the application are running on a single physical server within the cluster. It does so by ensuring that backup copies of data are being managed on different physical servers, so that if a physical server fails or is disconnected, all of the the data being managed by the failed server has backups ready to go on a different server.
Lastly, partitioning supports linear scalability of both data capacity and throughput. It accomplishes the scalability of data capacity by evenly balancing the data across all servers, so four servers can naturally manage two times as much data as two servers. Scalability of throughput is also a direct result of load-balancing the data across all servers, since as servers are added, each server is able to utilize its full processing power to manage a smaller and smaller percentage of the overall data set. For example, in a ten-server cluster each server has to manage 10% of the data operations, and – since Coherence uses a peer-to-peer architecture – 10% of those operations are coming from each server. With ten times that many servers (i.e. 100 servers), each server is managing only 1% of the data operations, and only 1% of those operations are coming from each server – but there are ten times as many servers, so the cluster is accomplishing ten times the total number of operations! In the 10-server example, if each of the ten servers was issuing 100 operations per second, they would each be sending 10 of those operations to each of the other servers, and the result would be that each server was receiving 100 operations (10x10) that it was responsible for processing. In the 100-server example, each would still be issuing 100 operations per second, but each would be sending only one operation to each of the other servers, so the result would be that each server was receiving 100 operations (100x1) that it was responsible for processing. This linear scalability is made possible by modern switched network architectures that provide backplanes that scale linearly to the number of ports on the switch, providing each port with dedicated fully-duplexed (upstream and downstream) bandwidth. Since each server is only sending and receiving 100 operations (in both the 10-server and 100-server examples), the network bandwidth utilization is roughly constant per port regardless of the number of servers in the cluster.
Session Management
One common use case for Coherence clustering is to manage user sessions (conversational state) in the cluster. This capability is provided by the Coherence*Web module, which is a built-in feature of Coherence. Coherence*Web provides linear scalability for HTTP Session Management in clusters of hundreds of production servers. It can achieve this linear scalability because at its core it is built on Coherence dynamic partitioning.
Session management highlights the scalability problem that typifies shared data sources: If an application could not share data across the servers, it would have to delegate that data management entirely to the shared store, which is typically the application's database. If the HTTP session were stored in the database, each HTTP request (in the absence of sticky load-balancing) would require a read from the database, causing the desired reads-per-second from the database to increase linearly with the size of the server cluster. Further, each HTTP request causes an update of its corresponding HTTP session, so regardless of sticky load balancing, to ensure that HTTP session data is not lost when a server fails the desired writes-per-second to the database will also increase linearly with the size of the server cluster. In both cases, the actual reads and writes per second that a database is capable of does not scale in relation to the number of servers requesting those reads and writes, and the database quickly becomes a bottleneck, forcing availability, reliability (e.g. asynchronous writes) and performance compromises. Additionally, related to performance, each read from a database has an associated latency, and that latency increases dramatically as the database experiences increasing load.
Coherence*Web, on the other hand, has the same latency in a 2-server cluster as it has in a 200-server cluster, since all HTTP session read operations that cannot be handled locally (e.g. locality as the result of the sticky load balancing) are spread out evenly across the rest of the cluster, and all update operations (which must be handled remotely to ensure survival of the HTTP sessions) are likewise spread out evently across the rest of the cluster. The result is linear scalability with constant latency, regardless of the size of the cluster.
Performance
Performance is the inverse of latency, and latency is the measurement of how long something takes to complete. If increasing performance is the goal, then getting rid of anything that has any latency is the solution. Obviously, it is impossible to get rid of all latencies, since the High Availability and reliability aspects of an application are counting on the underlying infrastructure, such as Coherence, to maintain reliable up-to-date back-ups of important information, which means that some operations (such as data modifications and pessimistic transactions) have unavoidable latencies. On the other hand, every remaining operation that could possibly have any latency needs to be targeted for elimination, and Coherence provides a large number of capabilities designed to do just that:
Replication
Just like partitioning dynamically load-balances data evenly across the entire server cluster, replication ensures that a desired set of data is up-to-date on every single server in the cluster at all times. Replication allows operations running on any server to obtain the data that they need locally, at basically no cost, because that data has already been replicated to that server. In other words, replication is a tool to guarantee locality of reference, and the end result is zero-latency access to replicated data.
Near Caching
Since replication works best for data that should be on all servers, it follows that replication is inefficient for data that an application would want to avoid copying to all servers. For example, data that changes all of the time and very large data sets are both poorly suited to replication, but both are excellently suited to partitioning, since it exhibits linear scale of data capacity and throughput.
The only downside of partitioning is that it introduces latency for data access, and in most applications the data access rate far out-weighs the data modification rate. To eliminate the latency associated with partitioned data access, near caching maintains frequently- and recently-used data from the partitioned cache on the specific servers that are accessing that data, and it keeps that data coherent by means of event-based invalidation. In other words, near caching keeps the most-likely-to-be-needed data near to where it will be used, thus providing good locality of access, yet backed up by the linear scalability of partitioning.
Write-Behind, Write-Coalescing and Write-Batching
Since the transactional throughput in the cluster is linearly scalable, the cost associated with data changes can be a fixed latency, typically in the range of a few milliseconds, and the total number of transactions per second is limited only by the size of the cluster. In one application, Coherence was able to achieve transaction rates close to a half-million transactions per second – and that on a cluster of commodity two-CPU servers.
Often, the data being managed by Coherence is actually a temporary copy of data that exists in an official System Of Record (SOR), such as a database. To avoid having the database become a transaction bottleneck, and to eliminate the latency of database updates, Coherence provides a Write-Behind capability, which allows the application to change data in the cluster, and those changes are asynchronously replayed to the application's database (or EIS). By managing the changes in a clustered cache (which has all of the High Availability, reliability and scalability attributes described previously,) the pending changes are immune to server failure and the total rate of changes scales linearly with the size of the cluster.
The Write-Behind functionality is implemented by queueing each data change; the queue contains a list of what changes needs to be written to the System Of Record. The duration of an item within the queue is configurable, and is referred to as the Write-Behind Delay. When data changes, it is added to the write-behind queue (if it is not already in the queue), and the queue entry is set to ripen after the configured Write-Behind Delay has passed. When the queue entry has ripened, the latest copy of the corresponding data is written to the System Of Record.
To avoid overwhelming the System Of Record, Coherence will replay only the latest copies of data to the database, thus coalescing many updates that occur to the same piece data into a single database operation. The longer the Write-Behind Delay, the more coalescing may occur. Additionally, if many different pieces of data have changed, all of those updates can be batched (e.g. using JDBC statement batching) into a single database operation. In this way, a massive breadth of changes (number of pieces of data changed) and depth of changes (number of times each was changed) can be bundled into a single database operation, which results in dramatically reduced load on the database. The batching is also fully configurable; one option, called the Write Batch Factor, even allows some of the queue entries that have not yet ripened to be included in the batched update.
Serviceability
Serviceability refers to the ease and extent of changes that can be affected without affecting availability. Coherence helps to increase an application's serviceability by allowing servers to be taken off-line without impacting the application availability. Those servers can be serviced and brought back online without any end-user or processing interruptions. Many configuration changes related to Coherence can also be made on a node-by-node basis in the same manner. With careful planning, even major application changes can be rolled into production – again, one node at a time – without interrupting the application.
Manageability
Manageability refers to the level of information that a running system provides, and the capability to tweak settings related to that information. For example, Coherence provides a cluster-wide view of management information via the standard JMX API, so that the entire cluster can be managed from a single server. The information provided includes hit and miss rates, cace sizes, read-, write- and write-behind statistics, and detailed information all the way down to the network packet level.
Additionally, Coherence allows applications to place their own management information – and expose their own tweakable settings – through the same clustered JMX implementation. The result is an application infrastructure that makes managing and monitoring a clustered application as simple as managing and monitoring a single server, and all through Java's standard management API.
Summary
There are a lot of challenges in building a highly available application that exhibits scalable performance and is both serviceable and manageable. While there are many ways to build distributed applications, only Coherence reliably clusters objects and data. Once objects and data are clustered by Coherence, all the servers in the cluster can access and modify those objects and that data, and the objects and data managed by Coherence will not be effected if and when servers fail. By providing a variety of advanced capabilities, each of which is configurable, and application can achieve the optimal balance of redundancy, scalability and performance, and do so within a manageable and serviceable environment.
Deliver events for changes as they occur
Overview
Coherence provides cache events using the JavaBean Event model. It is extremely simple to receive the events that you need, where you need them, regardless of where the changes are actually occurring in the cluster. Developers with any experience with the JavaBean model will have no difficulties working with events, even in a complex cluster.
Listener interface and Event object
In the JavaBeans Event model, there is an EventListener interface that all listeners must extend. Coherence provides a MapListener interface, which allows application logic to receive events when data in a Coherence cache is added, modified or removed:
public interface MapListener
extends EventListener
{
/**
* Invoked when a map entry has been inserted.
*
* @param evt the MapEvent carrying the insert information
*/
public void entryInserted(MapEvent evt);
/**
* Invoked when a map entry has been updated.
*
* @param evt the MapEvent carrying the update information
*/
public void entryUpdated(MapEvent evt);
/**
* Invoked when a map entry has been removed.
*
* @param evt the MapEvent carrying the delete information
*/
public void entryDeleted(MapEvent evt);
}
An application object that implements the MapListener interface can sign up for events from any Coherence cache or class that implements the ObservableMap interface, simply by passing an instance of the application's MapListener implementation to one of the addMapListener() methods.
The MapEvent object that is passed to the MapListener carries all of the necessary information about the event that has occurred, including the source (ObservableMap) that raised the event, the identity (key) that the event is related to, what the action was against that identity (insert, update or delete), what the old value was and what the new value is:
public class MapEvent
extends EventObject
{
/**
* Return an ObservableMap object on which this event has actually
* occured.
*
* @return an ObservableMap object
*/
public ObservableMap getMap()
/**
* Return this event's id. The event id is one of the ENTRY_*
* enumerated constants.
*
* @return an id
*/
public int getId()
/**
* Return a key associated with this event.
*
* @return a key
*/
public Object getKey()
/**
* Return an old value associated with this event.
* <p>
* The old value represents a value deleted from or updated in a map.
* It is always null for "insert" notifications.
*
* @return an old value
*/
public Object getOldValue()
/**
* Return a new value associated with this event.
* <p>
* The new value represents a new value inserted into or updated in
* a map. It is always null for "delete" notifications.
*
* @return a new value
*/
public Object getNewValue()
/**
* Return a String representation of this MapEvent object.
*
* @return a String representation of this MapEvent object
*/
public String toString()
/**
* This event indicates that an entry has been added to the map.
*/
public static final int ENTRY_INSERTED = 1;
/**
* This event indicates that an entry has been updated in the map.
*/
public static final int ENTRY_UPDATED = 2;
/**
* This event indicates that an entry has been removed from the map.
*/
public static final int ENTRY_DELETED = 3;
}
Caches and classes that support events
All Coherence caches implement ObservableMap; in fact, the NamedCache interface that is implemented by all Coherence caches extends the ObservableMap interface. That means that an application can sign up to receive events from any cache, regardless of whether that cache is local, partitioned, near, replicated, using read-through, write-through, write-behind, overflow, disk storage, etc.
 |
Regardless of the cache topology and the number of servers, and even if the modifications are being made by other servers, the events will be delivered to the application's listeners. |
In addition to the Coherence caches (those objects obtained through a Coherence cache factory), several other supporting classes in Coherence also implement the ObservableMap interface:
- ObservableHashMap
- LocalCache
- OverflowMap
- NearCache
- ReadWriteBackingMap
- AbstractSerializationCache, SerializationCache and SerializationPagedCache
- WrapperObservableMap, WrapperConcurrentMap and WrapperNamedCache
For a full list of published implementing classes, see the Coherence JavaDoc for ObservableMap.
Signing up for all events
To sign up for events, simply pass an object that implements the MapListener interface to one of the addMapListener methods on ObservableMap:
public void addMapListener(MapListener listener);
public void addMapListener(MapListener listener, Object oKey, boolean fLite);
public void addMapListener(MapListener listener, Filter filter, boolean fLite);
Let's create an example MapListener implementation:
/**
* A MapListener implementation that prints each event as it receives
* them.
*/
public static class EventPrinter
extends Base
implements MapListener
{
public void entryInserted(MapEvent evt)
{
out(evt);
}
public void entryUpdated(MapEvent evt)
{
out(evt);
}
public void entryDeleted(MapEvent evt)
{
out(evt);
}
}
Using this implementation, it is extremely simple to print out all events from any given cache (since all caches implement the ObservableMap interface):
cache.addMapListener(new EventPrinter());
Of course, to be able to later remove the listener, it is necessary to hold on to a reference to the listener:
Listener listener = new EventPrinter();
cache.addMapListener(listener);
m_listener = listener;
Later, to remove the listener:
Listener listener = m_listener;
if (listener != null)
{
cache.removeMapListener(listener);
m_listener = null; }
Each addMapListener method on the ObservableMap interface has a corresponding removeMapListener method. To remove a listener, use the removeMapListener method that corresponds to the addMapListener method that was used to add the listener.
Using an inner class as a MapListener
When creating an an inner class to use as a MapListener, or when implementing a MapListener that only listens to one or two types of events (inserts, updates or deletes), you can use the AbstractMapListener base class. For example, the following anonymous inner class prints out only the insert events for the cache:
cache.addMapListener(new AbstractMapListener()
{
public void entryInserted(MapEvent evt)
{
out(evt);
}
});
Another helpful base class for creating a MapListener is the MultiplexingMapListener, which routes all events to a single method for handling. For example, the EventPrinter example could be simplified to:
public static class EventPrinter
extends MultiplexingMapListener
{
public void onMapEvent(MapEvent evt)
{
out(evt);
}
}
Since only one method needs to be implemented to capture all events, the MultiplexingMapListener can also be very useful when creating an an inner class to use as a MapListener.
Configuring a MapListener for a Cache
If the listener should always be on a particular cache, then place it into the cache configuration using the listener element and Coherence will automatically add the listener when it configures the cache.
Signing up for Events on specific identities
Signing up for events that occur against specific identities (keys) is just as simple. For example, to print all events that occur against the Integer key "5":
cache.addMapListener(new EventPrinter(), new Integer(5), false);
So the following code would only trigger an event when the Integer key "5" is inserted or updated:
for (int i = 0; i < 10; ++i)
{
Integer key = new Integer(i);
String value = "test value for key " + i;
cache.put(key, value);
}
Filtering Events
Similar to listening to a particular key, it is possible to listen to particular events. Consider the following example:
public class DeletedFilter
implements Filter, Serializable
{
public boolean evaluate(Object o)
{
MapEvent evt = (MapEvent) o;
return evt.getId() == MapEvent.ENTRY_DELETED;
}
}
cache.addMapListener(new EventPrinter(), new DeletedFilter(), false);
 | Filtering events versus filtering cached data
When building a Filter for querying, the object that will be passed to the evaluate method of the Filter will be a value from the cache, or – if the Filter implements the EntryFilter interface – the entire Map.Entry from the cache. When building a Filter for filtering events for a MapListener, the object that will be passed to the evaluate method of the Filter will always be of type MapEvent.
For more information on how to use a query filter to listen to cache events, see the section below titled Advanced: Listening to Queries. |
The listener is added to the cache with a filter that allows the listener to only receive delete events. For example, if the following sequence of calls were made:
cache.put("hello", "world");
cache.put("hello", "again");
cache.remove("hello");
The result would be:
For more information, see the Advanced: Listening to Queries section below.
"Lite" Events
By default, Coherence provides both the old and the new value as part of an event. Consider the following example:
MapListener listener = new MultiplexingMapListener()
{
public void onMapEvent(MapEvent evt)
{
out("event has occurred: " + evt);
out("(the wire-size of the event would have been "
+ ExternalizableHelper.toBinary(evt).length()
+ " bytes.)");
}
};
cache.addMapListener(listener);
cache.put("test", new byte[1024]);
cache.put("test", new byte[2048]);
cache.remove("test");
The output from running the test shows that the first event carries the 1KB inserted value, the second event carries both the replaced 1KB value and the new 2KB value, and the third event carries the removed 2KB value:
When an application does not require the old and the new value to be included in the event, it can indicate that by requesting only "lite" events. When adding a listener, you can request lite events by using one of the two addMapListener methods that takes an additional boolean fLite parameter. In the above example, the only change would be:
cache.addMapListener(listener, (Filter) null, true);
 |
Obviously, a lite event's old value and new value may be null. However, even if you request lite events, the old and the new value may be included if there is no additional cost to generate and deliver the event. In other words, requesting that a MapListener receive lite events is simply a hint to the system that the MapListener does not need to know the old and new values for the event. |
Advanced: Listening to Queries
All Coherence caches support querying by any criteria. When an application queries for data from a cache, the result is a point-in-time snapshot, either as a set of identities ("keySet") or a set of identity/value pairs ("entrySet"). The mechanism for determining the contents of the resulting set is referred to as filtering, and it allows an application developer to construct queries of arbitrary complexity using a rich set of out-of-the-box filters (e.g. equals, less-than, like, between, etc.), or to provide their own custom filters (e.g. XPath).
The same filters that are used to query a cache can be used to listen to events from a cache. For example, in a trading system it is possible to query for all open "Order" objects for a particular trader:
NamedCache mapTrades = ...
Filter filter = new AndFilter(new EqualsFilter("getTrader", traderid),
new EqualsFilter("getStatus", Status.OPEN));
Set setOpenTrades = mapTrades.entrySet(filter);
To receive notifications of new trades being opened for that trader, closed by that trader or reassigned to or from another trader, the application can use the same filter:
trades.addMapListener(listener, new MapEventFilter(filter), true);
The MapEventFilter converts a query filter into an event filter.
 | Filtering events versus filtering cached data
When building a Filter for querying, the object that will be passed to the evaluate method of the Filter will be a value from the cache, or – if the Filter implements the EntryFilter interface – the entire Map.Entry from the cache. When building a Filter for filtering events for a MapListener, the object that will be passed to the evaluate method of the Filter will always be of type MapEvent.
The MapEventFilter converts a Filter that is used to do a query into a Filter that is used to filter events for a MapListener. In other words, the MapEventFilter is constructed from a Filter that queries a cache, and the resulting MapEventFilter is a filter that evaluates MapEvent objects by converting them into the objects that a query Filter would expect. |
The MapEventFilter has a number of very powerful options, allowing an application listener to receive only the events that it is specifically interested in. More importantly for scalability and performance, only the desired events have to be communicated over the network, and they are communicated only to the servers and clients that have expressed interest in those specific events! For example:
nMask = MapEventFilter.E_ALL;
trades.addMapListener(listener, new MapEventFilter(nMask, filter), true);
nMask = MapEventFilter.E_UPDATED_LEFT | MapEventFilter.E_DELETED;
trades.addMapListener(listener, new MapEventFilter(nMask, filter), true);
nMask = MapEventFilter.E_INSERTED | MapEventFilter.E_UPDATED_ENTERED;
trades.addMapListener(listener, new MapEventFilter(nMask, filter), true);
nMask = MapEventFilter.E_INSERTED;
trades.addMapListener(listener, new MapEventFilter(nMask, filter), true);
For more information on the various options supported, see the API documentation for MapEventFilter.
Advanced: Synthetic Events
Events usually reflect the changes being made to a cache. For example, one server is modifying one entry in a cache while another server is adding several items to a cache while a third server is removing an item from the same cache, all while fifty threads on each and every server in the cluster is accessing data from the same cache! All the modifying actions will produce events that any server within the cluster can choose to receive. We refer to these actions as client actions, and the events as being dispatched to clients, even though the "clients" in this case are actually servers. This is a natural concept in a true peer-to-peer architecture, such as a Coherence cluster: Each and every peer is both a client and a server, both consuming services from its peers and providing services to its peers. In a typical Java Enterprise application, a "peer" is an application server instance that is acting as a container for the application, and the "client" is that part of the application that is directly accessing and modifying the caches and listening to events from the caches.
Some events originate from within a cache itself. There are many examples, but the most common cases are:
- When entries automatically expire from a cache;
- When entries are evicted from a cache because the maximum size of the cache has been reached;
- When entries are transparently added to a cache as the result of a Read-Through operation;
- When entries in a cache are transparently updated as the result of a Read-Ahead or Refresh-Ahead operation.
Each of these represents a modification, but the modifications represent natural (and typically automatic) operations from within a cache. These events are referred to as synthetic events.
When necessary, an application can differentiate between client-induced and synthetic events simply by asking the event if it is synthetic. This information is carried on a sub-class of the MapEvent, called CacheEvent. Using the previous EventPrinter example, it is possible to print only the synthetic events:
public static class EventPrinter
extends MultiplexingMapListener
{
public void onMapEvent(MapEvent evt)
{
if (evt instanceof CacheEvent && ((CacheEvent) evt).isSynthetic())
{
out(evt);
)
}
}
For more information on this feature, see the API documentation for CacheEvent.
Advanced: Backing Map Events
While it is possible to listen to events from Coherence caches, each of which presents a local view of distributed, partitioned, replicated, near-cached, continuously-queried, read-through/write-through and/or write-behind data, it is also possible to peek behind the curtains, so to speak. Normally, the advice from the Wizard of Oz is sufficient:
 | "Pay no attention to the man behind the curtain!"
|
For some advanced use cases, it may be necessary to pay attention the man behind the curtain – or more correctly, to "listen to" the "map" behind the "service". Replication, partitioning and other approaches to managing data in a distributed environment are all distribution services. The service still has to have something in which to actually manage the data, and that something is called a "backing map".
Backing maps are configurable. If all the data for a particular cache should be kept in object form on the heap, then use an unlimited and non-expiring LocalCache (or a SafeHashMap if statistics are not required). If only a small number of items should be kept in memory, use a LocalCache. If data are to be read on demand from a database, then use a ReadWriteBackingMap (which knows how to read and write through an application's DAO implementation), and in turn give the ReadWriteBackingMap a backing map such as a SafeHashMap or a LocalCache to store its data in.
Some backing maps are observable. The events coming from these backing maps are not usually of direct interest to the appication. Instead, Coherence translates them into actions that must be taken (by Coherence) to keep data in sync and properly backed up, and it also translates them when appropriate into clustered events that are delivered throughout the cluster as requested by application listeners. For example, if a partitioned cache has a LocalCache as its backing map, and the local cache expires an entry, that event causes Coherence to expire all of the backup copies of that entry. Furthermore, if any listeners have been registered on the partitioned cache, and if the event matches their event filter(s), then that event will be delivered to those listeners on the servers where those listeners were registered.
In some advanced use cases, an application needs to process events on the server where the data are being maintained, and it needs to do so on the structure (backing map) that is actually managing the data. In these cases, if the backing map is an observable map, a listener can be configured on the backing map or one can be programmatically added to the backing map. (If the backing map is not observable, it can be made observable by wrapping it in an WrapperObservableMap.)
For more information on this feature, see the API documentation for BackingMapManager.
Advanced: Synchronous Event Listeners
Some events are delivered asynchronously, so that application listeners do not disrupt the cache services that are generating the events. In some rare scenarios, asynchronous delivery can cause ambiguity of the ordering of events compared to the results of ongoing operations. To guarantee that the cache API operations and the events are ordered as if the local view of the clustered system were single-threaded, a MapListener must implement the SynchronousListener marker interface.
One example in Coherence itself that uses synchronous listeners is the Near Cache, which can use events to invalidate locally cached data ("Seppuku").
For more information on this feature, see the API documentation for SynchronousListener.
Summary
Coherence provides an extremely rich event model for caches, providing the means for an application to request the specific events it requires, and the means to have those events delivered only to those parts of the application that require them.
Automatically manage dynamic cluster membership
Overview
Coherence manages cluster membership, automatically adding new servers to the cluster when they start up and automatically detecting their departure when they are shut down or fail. Applications have full access to this information, and can sign up to receive event notifications when members join and leave the cluster. Coherence also tracks all the services that each member is providing and consuming, and uses this information to plan for service resiliency in case of server failure, and to load-balance data management and other responsibilities across all members of the cluster.
Cluster and Service objects
From any cache, the application can obtain a reference to the local representation of a cache's service. From any service, the application can obtain a reference to the local representation of the cluster.
CacheService service = cache.getCacheService();
Cluster cluster = service.getCluster();
From the Cluster object, the application can determine the set of services that are running in the cluster:
for (Enumeration enum = cluster.getServiceNames(); enum.hasMoreElements(); )
{
String sName = (String) enum.nextElement();
ServiceInfo info = cluster.getServiceInfo(sName);
}
The ServiceInfo object provides information about the service, including its name, type, version and membership.
For more information on this feature, see the API documentation for NamedCache, CacheService, Service, ServiceInfo and Cluster.
Member object
The primary information that an application can determine about each member in the cluster is:
- The Member's IP address
- What date/time the Member joined the cluster
As an example, if there are four servers in the cluster with each server running one copy ("instance") of the application and all four instances of the application are clustered together, then the cluster is composed of four Members. From the Cluster object, the application can determine what the local Member is:
Member memberThis = cluster.getLocalMember();
From the Cluster object, the application can also determine the entire set of cluster members:
Set setMembers = cluster.getMemberSet();
From the ServiceInfo object, the application can determine the set of cluster members that are participating in that service:
ServiceInfo info = cluster.getServiceInfo(sName);
Set setMembers = info.getMemberSet();
For more information on this feature, see the [API documentation for Member.
Listener interface and Event object
To listen to cluster and/or service membership changes, the application places a listener on the desired Service. As discussed before, the Service can come from a cache:
Service service = cache.getCacheService();
The Service can also be looked up by its name:
Service service = cluster.getService(sName);
To receive membership events, the application implements a MemberListener. For example, the following listener example prints out all the membership events that it receives:
public class MemberEventPrinter
extends Base
implements MemberListener
{
public void memberJoined(MemberEvent evt)
{
out(evt);
}
public void memberLeaving(MemberEvent evt)
{
out(evt);
}
public void memberLeft(MemberEvent evt)
{
out(evt);
}
}
The MemberEvent object carries information about the event type (joined / leaving / left), the member that generated the event, and the service that acts as the source of the event. Additionally, the event provides a method, isLocal(), that indicates to the application that it is this member that is joining or leaving the cluster. This is useful for recognizing soft restarts in which an application automatically rejoins a cluster after a failure occurs. For example:
public class RejoinEventPrinter
extends Base
implements MemberListener
{
public void memberJoined(MemberEvent evt)
{
if (evt.isLocal())
{
out("this member just rejoined the cluster: " + evt);
}
}
public void memberLeaving(MemberEvent evt)
{
}
public void memberLeft(MemberEvent evt)
{
}
}
For more information on these feature, see the [API documentation for Service, MemberListener and MemberEvent.
Provide a Queryable Data Fabric
Overview
Oracle invented the concept of a data fabric with the introduction of the Coherence partitioned data management service in 2002. Since then, Forrester Research has labeled the combination of data virtualization, transparent and distributed EIS integration, queryability and uniform accessibility found in Coherence as an information fabric. The term fabric comes from a 2-dimensional illustration of interconnects, as in a switched fabric. The purpose of a fabric architecture is that all points within a fabric have a direct interconnect with all other points.
Data Fabric
An information fabric, or the more simple form called a data fabric or data grid, uses a switched fabric concept as the basis for managing data in a distributed environment. Also referred to as a dynamic mesh architecture, Coherence automatically and dynamically forms a reliable, increasingly resilient switched fabric composed of any number of servers within a grid environment. Consider the attributes and benefits of this architecture:
- The aggregate data throughput of the fabric is linearly proportional to the number of servers;
- The in-memory data capacity and data-indexing capacity of the fabric is linearly proportional to the number of servers;
- The aggregate I/O throughput for disk-based overflow and disk-based storage of data is linearly proportional to the number of servers;
- The resiliency of the fabric increases with the extent of the fabric, resulting in each server being responsible for only 1/n of the failover responsibility for a fabric with an extent of n servers;
- If the fabric is servicing clients, such as trading systems, the aggregage maximum number of clients that can be served is linearly proportional to the number of servers.
Coherence accomplishes these technical feats through a variety of algorithms:
- Coherence dynamically partitions data across all data fabric nodes;
- Since each data fabric node has a configurable maximum amount of data that it will manage, the capacity of the data fabric is linearly proportional to the number of data fabric nodes;
- Since the partitioning is automatic and load-balancing, each data fabric node ends up with its fair share of the data management responsibilities, allowing the throughput (in terms of network throughput, disk I/O throughput, query throughput, etc.) to scale linearly with the number of data fabric nodes;
- Coherence maintains a configurable level of redundancy of data, automatically eliminating single points of failure (SPOFs) by ensuring that data is kept synchronously up-to-date in multiple data fabric nodes;
- Coherence spreads out the responsibility for data redundancy in a dynamically load-balanced manner so that each server backs up a small amount of data from many other servers, instead of backing up all of the data from one particular server, thus amortizing the impact of a server failure across the entire data fabric;
- Each data fabric node can handle a large number of client connections, which can be load-balanced by a hardware load balancer.
EIS and Database Integration
The Coherence information fabric can automatically load data on demand from an underlying database or EIS using automatic read-through functionality. If data in the fabric are modified, the same functionality allows that data to be synchronously updated in the database, or queued for asynchronous write-behind.
Coherence automatically partitions data access across the data fabric, resulting in load-balanced data accesses and efficient use of database and EIS connectivity. Furthermore, the read-ahead and write-behind capabilities can cut data access latencies to near-zero levels and insulate the application from temporary database and EIS failures.
 | Coherence solves the data bottleneck for large-scale compute grids
In large-scale compute grids, such as in DataSynapse financial grids and biotech grids, the bottleneck for most compute processes is in loading a data set and making it available to the compute engines that require it. By layering a Coherence data fabric onto (or beside) a compute grid, these data sets can be maintained in memory at all times, and Coherence can feed the data in parallel at close to wire speed to all of the compute nodes. In a large-scale deployment, Coherence can provide several thousand times the aggregate data throughput of the underlying data source. |
Queryable
The Coherence information fabric supports querying from any server in the fabric or any client of the fabric. The queries can be performed using any criteria, including custom criteria such as XPath queries and full text searches. When Coherence partitioning is used to manage the data, the query is processed in parallel across the entire fabric (i.e. the query is also partitioned), resulting in an data query engine that can scale its throughput up to fabrics of thousands of servers. For example, in a trading system it is possible to query for all open "Order" objects for a particular trader:
NamedCache mapTrades = ...
Filter filter = new AndFilter(new EqualsFilter("getTrader", traderid),
new EqualsFilter("getStatus", Status.OPEN));
Set setOpenTrades = mapTrades.entrySet(filter);
When an application queries for data from the fabric, the result is a point-in-time snapshot. Additionally, the query results can be kept up-to-date by placing a listener on the query itself or by using the Coherence Continuous Query feature.
Continuous Query
While it is possible to obtain a point in time query result from a Coherence data fabric, and it is possible to receive events that would change the result of that query, Coherence provides a feature that combines a query result with a continuous stream of related events that maintain the query result in a real-time fashion. This capability is called Continuous Query, because it has the same effect as if the desired query had zero latency and the query were repeated several times every millisecond!
Coherence implements Continuous Query using a combination of its data fabric parallel query capability and its real-time event-filtering and streaming. The result is support for thousands of client application instances, such as trading desktops. Using the previous trading system example, it can be converted to a Continuous Query with only one a single line of code changed:
NamedCache mapTrades = ...
Filter filter = new AndFilter(new EqualsFilter("getTrader", traderid),
new EqualsFilter("getStatus", Status.OPEN));
NamedCache mapOpenTrades = new ContinuousQueryCache(mapTrades, filter);
The result of the Continuous Query is maintained locally, and optionally all of corresponding data can be cached locally as well.
Summary
Coherence is successfully deployed as a large-scale data fabric for many of the world's largest financial, telecommunications, logistics, travel and media organizations. With unlimited scalability, the highest levels of availability, close to zero latency, an incredibly rich set of capabilities and a sterling reputation for quality, Coherence is the Information Fabric of choice.
Provide a Data Grid
Overview
Coherence provides the ideal infrastructure for building Data Grid services, as well as the client and server-based applications that utilize a Data Grid. At a basic level, Coherence can manage an immense amount of data across a large number of servers in a grid; it can provide close to zero latency access for that data; it supports parallel queries across that data; and it supports integration with database and EIS systems that act as the system of record for that data. For more information on the infrastructure for the Data Grid features in Coherence, refer to the discussion on Data Fabric capabilities. Additionally, Coherence provides a number of services that are ideal for building effective data grids.
 | All of the Data Grid capabilities described below are features of Coherence Enterprise Edition and higher.
|
Targeted Execution
Coherence provides for the ability to execute an agent against an entry in any map of data managed by the Data Grid:
In the case of partitioned data, the agent executes on the grid node that owns the data to execute against. This means that the queueing, concurrency management, agent execution, data access by the agent and data modification by the agent all occur on that grid node. (Only the synchronous backup of the resultant data modification, if any, requires additional network traffic.) For many processing purposes, it is much more efficient to move the serialized form of the agent (usually only a few hundred bytes, at most) than to handle distributed concurrency control, coherency and data updates.
For request/response processing, the agent returns a result:
Object oResult = map.invoke(key, agent);
In other words, Coherence as a Data Grid will determine the location to execute the agent based on the configuration for the data topology, move the agent there, execute the agent (automatically handling concurrency control for the item while executing the agent), back up the modifications if any, and return a result.
Parallel Execution
Coherence additionally provides for the ability to execute an agent against an entire collection of entries. In a partitioned Data Grid, the execution occurs in parallel, meaning that the more nodes that are in the grid, the broader the work is load-balanced across the Data Grid:
map.invokeAll(collectionKeys, agent);
For request/response processing, the agent returns one result for each key processed:
Map mapResults = map.invokeAll(collectionKeys, agent);
In other words, Coherence determines the optimal location(s) to execute the agent based on the configuration for the data topology, moves the agent there, executes the agent (automatically handling concurrency control for the item(s) while executing the agent), backing up the modifications if any, and returning the coalesced results.
Query-Based Execution
As discussed in the queryable data fabric topic, Coherence supports the ability to query across the entire data grid. For example, in a trading system it is possible to query for all open "Order" objects for a particular trader:
NamedCache map = CacheFactory.getCache("trades");
Filter filter = new AndFilter(new EqualsFilter("getTrader", traderid),
new EqualsFilter("getStatus", Status.OPEN));
Set setOpenTradeIds = mapTrades.keySet(filter);
By combining this feature with Parallel Execution in the data grid, Coherence provides for the ability to execute an agent against a query. As in the previous section, the execution occurs in parallel, and instead of returning the identities or entries that match the query, Coherence executes the agents against the entries:
map.invokeAll(filter, agent);
For request/response processing, the agent returns one result for each key processed:
Map mapResults = map.invokeAll(filter, agent);
In other words, Coherence combines its Parallel Query and its Parallel Execution together to achieve query-based agent invocation against a Data Grid.
Data-Grid-Wide Execution
Passing an instance of AlwaysFilter (or a null) to the invokeAll method will cause the passed agent to be executed against all entries in the InvocableMap:
map.invokeAll((Filter) null, agent);
As with the other types of agent invocation, request/response processing is supported:
Map mapResults = map.invokeAll((Filter) null, agent);
In other words, with a single line of code, an application can process all the data spread across a particular map in the Data Grid.
Agents for Targeted, Parallel and Query-Based Execution
An agent implements the EntryProcessor interface, typically by extending the AbstractProcessor class.
A number of agents are included with Coherence, including:
- AbstractProcessor - an abstract base class for building an EntryProcessor
- ExtractorProcessor - extracts and returns a specific value (such as a property value) from an object stored in an InvocableMap
- CompositeProcessor - bundles together a collection of EntryProcessor objects that are invoked sequentially against the same Entry
- ConditionalProcessor - conditionally invokes an EntryProcessor if a Filter against the Entry-to-process evaluates to true
- PropertyProcessor - an abstract base class for EntryProcessor implementations that depend on a PropertyManipulator
- NumberIncrementor - pre- or post-increments any property of a primitive integral type, as well as Byte, Short, Integer, Long, Float, Double, BigInteger, BigDecimal
- NumberMultiplier - multiplies any property of a primitive integral type, as well as Byte, Short, Integer, Long, Float, Double, BigInteger, BigDecimal, and returns either the previous or new value
The EntryProcessor interface (contained within the InvocableMap interface) contains only two methods:
/**
* An invocable agent that operates against the Entry objects within a
* Map.
*/
public interface EntryProcessor
extends Serializable
{
/**
* Process a Map Entry.
*
* @param entry the Entry to process
*
* @return the result of the processing, if any
*/
public Object process(Entry entry);
/**
* Process a Set of InvocableMap Entry objects. This method is
* semantically equivalent to:
* <pre>
* Map mapResults = new ListMap();
* for (Iterator iter = setEntries.iterator(); iter.hasNext(); )
* {
* Entry entry = (Entry) iter.next();
* mapResults.put(entry.getKey(), process(entry));
* }
* return mapResults;
* </pre>
*
* @param setEntries a read-only Set of InvocableMap Entry objects to
* process
*
* @return a Map containing the results of the processing, up to one
* entry for each InvocableMap Entry that was processed, keyed
* by the keys of the Map that were processed, with a
* corresponding value being the result of the processing for
* each key
*/
public Map processAll(Set setEntries);
}
(The AbstractProcessor implements the processAll method as described in the JavaDoc above.)
The InvocableMap.Entry that is passed to an EntryProcessor is an extension of the Map.Entry interface that allows an EntryProcessor implementation to obtain the necessary information about the entry and to make the necessary modifications in the most efficient manner possible:
/**
* An InvocableMap Entry contains additional information and exposes
* additional operations that the basic Map Entry does not. It allows
* non-existent entries to be represented, thus allowing their optional
* creation. It allows existent entries to be removed from the Map. It
* supports a number of optimizations that can ultimately be mapped
* through to indexes and other data structures of the underlying Map.
*/
public interface Entry
extends Map.Entry
{
/**
* Return the key corresponding to this entry. The resultant key does
* not necessarily exist within the containing Map, which is to say
* that <tt>InvocableMap.this.containsKey(getKey)</tt> could return
* false. To test for the presence of this key within the Map, use
* {@link #isPresent}, and to create the entry for the key, use
* {@link #setValue}.
*
* @return the key corresponding to this entry; may be null if the
* underlying Map supports null keys
*/
public Object getKey();
/**
* Return the value corresponding to this entry. If the entry does
* not exist, then the value will be null. To differentiate between
* a null value and a non-existent entry, use {@link #isPresent}.
* <p/>
* <b>Note:</b> any modifications to the value retrieved using this
* method are not guaranteed to persist unless followed by a
* {@link #setValue} or {@link #update} call.
*
* @return the value corresponding to this entry; may be null if the
* value is null or if the Entry does not exist in the Map
*/
public Object getValue();
/**
* Store the value corresponding to this entry. If the entry does
* not exist, then the entry will be created by invoking this method,
* even with a null value (assuming the Map supports null values).
*
* @param oValue the new value for this Entry
*
* @return the previous value of this Entry, or null if the Entry did
* not exist
*/
public Object setValue(Object oValue);
/**
* Store the value corresponding to this entry. If the entry does
* not exist, then the entry will be created by invoking this method,
* even with a null value (assuming the Map supports null values).
* <p/>
* Unlike the other form of {@link #setValue(Object) setValue}, this
* form does not return the previous value, and as a result may be
* significantly less expensive (in terms of cost of execution) for
* certain Map implementations.
*
* @param oValue the new value for this Entry
* @param fSynthetic pass true only if the insertion into or
* modification of the Map should be treated as a
* synthetic event
*/
public void setValue(Object oValue, boolean fSynthetic);
/**
* Extract a value out of the Entry's value. Calling this method is
* semantically equivalent to
* <tt>extractor.extract(entry.getValue())</tt>, but this method may
* be significantly less expensive because the resultant value may be
* obtained from a forward index, for example.
*
* @param extractor a ValueExtractor to apply to the Entry's value
*
* @return the extracted value
*/
public Object extract(ValueExtractor extractor);
/**
* Update the Entry's value. Calling this method is semantically
* equivalent to:
* <pre>
* Object oTarget = entry.getValue();
* updater.update(oTarget, oValue);
* entry.setValue(oTarget, false);
* </pre>
* The benefit of using this method is that it may allow the Entry
* implementation to significantly optimize the operation, such as
* for purposes of delta updates and backup maintenance.
*
* @param updater a ValueUpdater used to modify the Entry's value
*/
public void update(ValueUpdater updater, Object oValue);
/**
* Determine if this Entry exists in the Map. If the Entry is not
* present, it can be created by calling {@link #setValue} or
* {@link #setValue}. If the Entry is present, it can be destroyed by
* calling {@link #remove}.
*
* @return true iff this Entry is existent in the containing Map
*/
public boolean isPresent();
/**
* Remove this Entry from the Map if it is present in the Map.
* <p/>
* This method supports both the operation corresponding to
* {@link Map#remove} as well as synthetic operations such as
* eviction. If the containing Map does not differentiate between
* the two, then this method will always be identical to
* <tt>InvocableMap.this.remove(getKey())</tt>.
*
* @param fSynthetic pass true only if the removal from the Map
* should be treated as a synthetic event
*/
public void remove(boolean fSynthetic);
}
Data Grid Aggregation
While the above agent discussion correspond to scalar agents, the InvocableMap interface also supports aggregation:
/**
* Perform an aggregating operation against the entries specified by the
* passed keys.
*
* @param collKeys the Collection of keys that specify the entries within
* this Map to aggregate across
* @param agent the EntryAggregator that is used to aggregate across
* the specified entries of this Map
*
* @return the result of the aggregation
*/
public Object aggregate(Collection collKeys, EntryAggregator agent);
/**
* Perform an aggregating operation against the set of entries that are
* selected by the given Filter.
* <p/>
* <b>Note:</b> calling this method on partitioned caches requires a
* Coherence Enterprise Edition (or higher) license.
*
* @param filter the Filter that is used to select entries within this
* Map to aggregate across
* @param agent the EntryAggregator that is used to aggregate across
* the selected entries of this Map
*
* @return the result of the aggregation
*/
public Object aggregate(Filter filter, EntryAggregator agent);
A simple EntryAggregator processes a set of InvocableMap.Entry objects to achieve a result:
/**
* An EntryAggregator represents processing that can be directed to occur
* against some subset of the entries in an InvocableMap, resulting in a
* aggregated result. Common examples of aggregation include functions
* such as min(), max() and avg(). However, the concept of aggregation
* applies to any process that needs to evaluate a group of entries to
* come up with a single answer.
*/
public interface EntryAggregator
extends Serializable
{
/**
* Process a set of InvocableMap Entry objects in order to produce an
* aggregated result.
*
* @param setEntries a Set of read-only InvocableMap Entry objects to
* aggregate
*
* @return the aggregated result from processing the entries
*/
public Object aggregate(Set setEntries);
}
For efficient execution in a Data Grid, an aggregation process must be designed to operate in a parallel manner.
/**
* A ParallelAwareAggregator is an advanced extension to EntryAggregator
* that is explicitly capable of being run in parallel, for example in a
* distributed environment.
*/
public interface ParallelAwareAggregator
extends EntryAggregator
{
/**
* Get an aggregator that can take the place of this aggregator in
* situations in which the InvocableMap can aggregate in parallel.
*
* @return the aggregator that will be run in parallel
*/
public EntryAggregator getParallelAggregator();
/**
* Aggregate the results of the parallel aggregations.
*
* @return the aggregation of the parallel aggregation results
*/
public Object aggregateResults(Collection collResults);
}
Coherence comes with all of the natural aggregation functions, including:
Node-Based Execution
Coherence provides an Invocation Service which allows execution of single-pass agents (called Invocable objects) anywhere within the grid. The agents can be executed on any particular node of the grid, in parallel on any particular set of nodes in the grid, or in parallel on all nodes of the grid.
An invocation service is configured using the invocation-scheme element in the cache configuration file. Using the name of the service, the application can easily obtain a reference to the service:
InvocationService service = CacheFactory.getInvocationService("agents");
Agents are simply runnable classes that are part of the application. The simplest example is a simple agent that is designed to request a GC from the JVM:
/**
* Agent that issues a garbage collection.
*/
public class GCAgent
extends AbstractInvocable
{
public void run()
{
System.gc();
}
}
To execute that agent across the entire cluster, it takes one line of code:
service.execute(new GCAgent(), null, null);
Here is an example of an agent that supports a grid-wide request/response model:
/**
* Agent that determines how much free memory a grid node has.
*/
public class FreeMemAgent
extends AbstractInvocable
{
public void run()
{
Runtime runtime = Runtime.getRuntime();
int cbFree = runtime.freeMemory();
int cbTotal = runtime.totalMemory();
setResult(new int[] {cbFree, cbTotal});
}
}
To execute that agent across the entire grid and retrieve all the results from it, it still takes only one line of code:
Map map = service.query(new FreeMemAgent(), null);
While it is easy to do a grid-wide request/response, it takes a bit more code to print out the results:
Iterator iter = map.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry) iter.next();
Member member = (Member) entry.getKey();
int[] anInfo = (int[]) entry.getValue();
if (anInfo != null) System.out.println("Member " + member + " has "
+ anInfo[0] + " bytes free out of "
+ anInfo[1] + " bytes total");
}
The agent operations can be stateful, which means that their invocation state is serialized and transmitted to the grid nodes on which the agent is to be run.
/**
* Agent that carries some state with it.
*/
public class StatefulAgent
extends AbstractInvocable
{
public StatefulAgent(String sKey)
{
m_sKey = sKey;
}
public void run()
{
String sKey = m_sKey;
}
private String m_sKey;
}
Work Manager
Coherence provides a grid-enabled implementation of the IBM and BEA CommonJ Work Manager, which is the basis for JSR-237. Once JSR-237 is complete, Oracle has committed to support the standardized J2EE API for Work Manager as well.
Using a Work Manager, an application can submit a collection of work that needs to be executed. The Work Manager distributes that work in such a way that it is executed in parallel, typically across the grid. In other words, if there are ten work items submitted and ten servers in the grid, then each server will likely process one work item. Further, the distribution of work items across the grid can be tailored, so that certain servers (e.g. one that acts as a gateway to a particular mainframe service) will be the first choice to run certain work items, for sake of efficiency and locality of data.
The application can then wait for the work to be completed, and can provide a timeout for how long it is willing to wait. The API for this purpose is quite powerful, allowing an application to wait for the first work item to complete, or for a specified set of the work items to complete. By combining methods from this API, it is possible to do things like "Here are 10 items to execute; for these 7 unimportant items, wait no more than 5 seconds, and for these 3 important items, wait no more than 30 seconds":
Work[] aWork = ...
Collection collBigItems = new ArrayList();
Collection collAllItems = new ArrayList();
for (int i = 0, c = aWork.length; i < c; ++i)
{
WorkItem item = manager.schedule(aWork[i]);
if (i < 3)
{
collBigItems.add(item);
}
collAllItems.add(item);
}
Collection collDone = manager.waitForAll(collAllItems, 5000L);
if (!collDone.containsAll(collBigItems))
{
manager.waitForAll(collBigItems, 25000L);
}
Of course, the best descriptions come from real-world production usage:
Our primary use case for the Work Manager is to allow our application to serve coarse-grained service requests using our blade infrastructure in a standards-based way. We often have what appears to be a simple request, like "give me this family's information." In reality, however, this request expands into a large number of requests to several diverse back-end data sources consisting of web services, RDMBS calls, etc. This use case expands into two different but related problems that we are looking to the distributed version of the work manager to solve.
1. How do we take a coarse-grained request that expands into several fine-grained requests and execute them in parallel to avoid blocking the caller for an unreasonable time? In the above example, we may have to make upwards of 100 calls to various places to retrieve the information. Since J2EE has no legal threading model, and since the threading we observed when trying a message-based approach to this was unacceptable, we decided to use the Coherence Work Manager implementation.
2. Given that we want to make many external system calls in parallel while still leveraging low-cost blades, we are hoping that fanning the required work across many dual processor (logically 4-processor because of hyperthreading) machines allows us to scale an inherently vertical scalability problem with horizontal scalability at the hardware level. We think this is reasonable because the cost to marshall the request to a remote Work Manager instance is small compared to the cost to execute the service, which usually involves dozens or hundreds of milliseconds.
For more information on the Work Manager Specification and API, see Timer and Work Manager for Application Servers on the BEA dev2dev web site and JSR 237.
Summary
Coherence provides an extensive set of capabilities that make Data Grid services simple, seamless and seriously scalable. While the data fabric provides an entire unified view of the complete data domain, the Data Grid features enable applications to take advantage of the partitioning of data that Coherence provides in a scale-out environment.
Real Time Client - RTC
Overview
The Coherence Real Time Client provides secure and scalable client access from desktop applications into a Coherence Data Grid. Coherence RTC extends the Data Grid to the Desktop, providing the same core API as the rest of the Coherence product line. As of Coherence 3.2, Coherence RTC is licensed as Coherence Real Time Client.
Connectivity into the Coherence Data Grid is achieved via Coherence*Extend technology, which enables a client application to connect to a particular server within the Data Grid. Since the connections are load-balanced across all of the servers in the Data Grid, this approach to connectivity can scale to support tens of thousands of desktop systems.
Uses
The primary use case for Coherence RTC is to provide desktop clients with read-only/read-mostly access to data held in a Coherence cluster. Clients can query clustered caches and receive real-time updates as the data changes. Clients may also initiate server-side data manipulation tasks, including aggregations (using
com.tangosol.util.InvocableMap.EntryAggregator) and processing (using
com.tangosol.util.InvocableMap.EntryProcessor).
Cache Access
Normally, desktop applications are granted only read access to the data being managed by the Data Grid (delegating cache updates to Data Grid Agents), although it is possible to enable direct read/write access.
Local Caches
While the desktop application can directly access the caches managed by the Data Grid, that may be inefficient depending on the network infrastructure. For efficiency, the desktop application can use both Near Caching and Continuous Query Caching to maintain cache data locally.
Event Notification
Using the standard Coherence event model, data changes that occur within the Data Grid are visible to the desktop application. Since the desktop application indicates the exact events that it is interested in, only those events are actually delivered over the wire, resulting in efficient use of network bandwidth and client processing.
Agent Invocation
Since the desktop application will likely only have read-only access, any manipulation of data is done within the Data Grid itself; the mechanism for this is the Data Grid Agent, which is supported by the
InvocableMap API.
Desktops may invoke tasks, aggregators and processors for server-side cached objects using InvocableMap.
Connection Failover
If the server to which the desktop application is attached happens to fail, the connection is automatically re-established to another server, and then any locally cached data is re-synced with the cluster.
Using Coherence and BEA WebLogic Portal
Introduction
Coherence integrates closely with BEATM
WebLogicTM
Portal to provide WAN-capable clustered session management and caching for portal applications. Specifically, Coherence includes the following integration points:
- Coherence*Web support for WebLogic Portal
- P13N CacheProvider SPI implementation
- A blueprint for efficiently sharing data between WSRP-federated portals that leverages Coherence and the WebLogic Portal Custom Data Transfer mechanism
 | Requires WebLogic Portal 8.1.6
Please note that these features require WebLogic Portal 8.1.6. Additionally for Coherence*Web to work correctly with WebLogic Portal, a patch for issue CR314006 must be applied to all WebLogic Portal installations that will be running Coherence*Web. You can request this patch from your BEA sales representative. After installing this patch, you must add the following system properties to all WebLogic Portal instances, including the admin server:
-Dcom.bea.p13n.servlet.wrapper=com.tangosol.coherence.servlet.api22.ServletWrapper
-Dcom.bea.p13n.servlet.wrapper.param=coherence-servlet-class
|
Coherence*Web for WebLogic Portal
When Coherence*Web is installed into a WebLogic Portal web application, everything that the portal framework and portlets place into the HttpSession will be managed by Coherence. This has several benefits as described in the following article:
http://www.oracle.com/technology/pub/articles/dev2arch/2005/11/federated-portal-cache.html
Additionally, combining Coherence*Web and WebLogic Portal gives you extreme flexibility in your choice of a session management cache topology. For example, if you find that your Portal servers are bumping into the 4GB heap limit (on 32-bit JVMs) or are experiencing slow GC times, you can leverage a cache client/server topology to move all HttpSession state out of your Portal JVMs and into one or more dedicated Coherence cache servers, thus reducing your Portal JVM heap size and GC times. Also, you can leverage the Coherence Management Framework to closely monitor HttpSession-specific statistics to better tune your Coherence*Web and session management cache settings.
For details on installing Coherence*Web into a WebLogic Portal web application, please see Installing Coherence*Web Session Management Module.
P13N CacheProvider SPI Implementation
Internally, WebLogic Portal uses its own caching service to cache portal, personalization, and commerce data as described here:
http://e-docs.bea.com/wlp/docs81/javadoc/com/bea/p13n/cache/package-summary.html
WebLogic Portal 8.1.6 includes an SPI for the P13N caching service that can be implemented by third party cache vendors. Coherence includes a P13N CacheProvider SPI implementation that - when installed into a WebLogic Portal application - has the same benefits for serializable WebLogic Portal data as Coherence*Web has for HttpSession state, all without requiring code changes. Additionally, the Coherence CacheProvider allows your portlets to leverage Coherence caching services simply by using the standard P13N Cache API.
To install the Coherence P13N CacheProvider, simply copy the coherence-wlp.jar, coherence.jar and tangosol.jar libraries included in the lib directory of the Coherence installation to the APP-INF/lib directory of your WebLogic Portal application. On startup, WebLogic Portal will automatically discover the Coherence CacheProvider and transparently use it to cache data.
Please see the JavaDoc for the
PortalCacheProvider class for details on configuring the Coherence CacheProvider and Coherence caches used by the provider. Additionally, please see the following document for a list of some of the caches used by WebLogic Portal:
http://e-docs.bea.com/wlp/docs81/perftune/apenB.html
Sharing Data Between WSRP-Federated Portals Using Coherence
The Web Services for Remote Portlets (WSRP) protocol was designed to support the federation of portals hosted by arbitrary portal servers and server clusters. Developers use WSRP to aggregate content and the user interface (UI) from various portlets hosted by other remote portals. By itself, though, WSRP does not address the challenge of implementing scalable, reliable, and high-performance federated portals that create, access, and manage the lifecycle of data shared by distributed portlets. Fortunately, BEA WebLogic Portal provides an extension to the WSRP specification that — when coupled with Oracle Coherence — allows WSRP Consumers and Producers to create, view, modify, and control concurrent access to shared, scoped data in a scalable, reliable, and highly performant manner.
See the following document for complete details:
http://dev2dev.bea.com/pub/a/2005/11/federated-portal-cache.html
Using Coherence and JPA
Introduction
The Java Persistence API (JPA) is the primary standard for O/R mapping and enterprise Java persistence. A number of open source and commercial implementations exist and still others are being developed.
Coherence ships a CacheStore implementation that uses JPA to load and store objects to the database. This document describes how to configure and use this CacheStore.
Limitations
A JPA provider is not shipped with Coherence, but is easy to procure. The Reference Implementation for JPA is called TopLink Essentials and is free and open source. It is available from the Oracle Technology Network (OTN) at http://otn.oracle.com/jpa
Only resource-local and bootstrapped entity managers are currently supported. Container-managed entity managers and those that use JTA transactions are not supported at this time.
Conventions
This document refers to the following Java classes and interfaces:
com.tangosol.coherence.jpa.JpaCacheLoader
com.tangosol.coherence.jpa.JpaCacheStore
com.tangosol.net.NamedCache (extends java.util.Map)
com.tangosol.net.cache.CacheLoader
com.tangosol.net.cache.CacheStore
As the CacheStore interface extends CacheLoader, the term "CacheStore" will be used generically to refer to both interfaces (the appropriate interface being determined by whether read-only or read-write support is required). Similarly, "JpaCacheStore" will refer to both implementations.
The Coherence cache configuration file is referred to as the coherence-cache-config.xml (the default name). The JPA persistence implementation is referred to simply as the JPA provider or JPA vendor. The JPA runtime configuration file is referred to as the persistence.xml, and the JPA O/R mapping file is referred to as the orm.xml (the default name).
Using the Coherence JpaCacheStore
Overview
The JPA is a standard API for mapping, querying and storing Java objects to a database. The characteristics of the different JPA implementations may differ, however, when it comes to caching, threading, and overall performance. TopLink Essentials is a high-performing JPA implementation that meets the performance needs of most applications.
Coherence includes a default entity-based CacheStore implementation, JpaCacheStore (and a corresponding CacheLoader implementation, JpaCacheLoader). Other information may be found in the Javadoc for the implementing classes.
Mapping the Persistent Classes
The first step in being able to load and store objects through the CacheStore is to ensure that the classes are mapped to the database. JPA mappings are standard, and hence may be specified the same way for any and all JPA providers.
Entities may be mapped either by annotating the entity classes or by adding an orm.xml or other XML mapping file(s). See the JPA vendor documentation for more on how to map JPA entities.
Configuring JPA
A typical JPA configuration involves making changes to the persistence.xml. Within the persistence.xml are the properties that dictate runtime operation. Below is a sample persistence.xml showing the typical properties that are set.
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="EmpUnit" transaction-type="RESOURCE_LOCAL">
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<class>com.acme.Employee</class>
<properties>
<property name="toplink.jdbc.driver" value="oracle.jdbc.OracleDriver"/>
<property name="toplink.jdbc.url" value="jdbc:oracle:thin:@localhost:1521:XE"/>
<property name="toplink.jdbc.user" value="scott"/>
<property name="toplink.jdbc.password" value="tiger"/>
</properties>
</persistence-unit>
</persistence>
The transaction type should be set to RESOURCE_LOCAL and the four JDBC properties should contain the appropriate values for connecting and logging in to the database being used. Classes that are mapped using JPA annotations should be listed in <class> elements.
Configuring Coherence
A coherence-cache-config.xml must be specified to override the default Coherence settings and define the JpaCacheStore caching scheme. The caching scheme should include a <cachestore-scheme> element that lists the JpaCacheStore class and includes three parameters.
The first parameter is the entity name of the entity being stored. Unless it is explictly overridden in JPA it will be the unqualified name of the entity class. In the example cache scheme listed below we make use of the built-in Coherence macro {cache-name} that translates to the name of the cache that is constructing and using the CacheStore. This works because a separate cache should be used for each type of persistent entity and we will ensure that the name of each cache will be set to the name of the entity that is being stored in it.
The second parameter is the fully qualified name of the entity class. If the classes are all in the same package and use the default JPA entity names then we can once again use the {cache-name} macro to fill in the part that is variable across the different entity types. In this way the same caching scheme can be used for all of the entities that are cached within the same persistence unit.
The third parameter is the persistence unit name, which should be the same as the name specified in the persistence.xml.
The various named caches are then directed to use the JPA caching scheme. The following is a sample coherence-cache-config.xml used to define a NamedCache called "Employee" that caches instances of the Employee class. To define additional entity caches for more classes then more <cache-mapping> elements may be added.
<cache-config>
<caching-scheme-mapping>
<cache-mapping>
<!—- Set the name of the cache to be the entity name -->
<cache-name>Employee</cache-name>
<!—- Configure this cache to use the scheme defined below -->
<scheme-name>jpa-distributed</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<distributed-scheme>
<scheme-name>jpa-distributed</scheme-name>
<service-name>JpaDistributedCache</service-name>
<backing-map-scheme>
<read-write-backing-map-scheme>
<internal-cache-scheme>
<local-scheme/>
</internal-cache-scheme>
<!— Define the cache scheme -->
<cachestore-scheme>
<class-scheme>
<class-name>
com.tangosol.coherence.jpa.JpaCacheStore
</class-name>
<init-params>
<!—- This param is the entity name -->
<init-param>
<param-type>java.lang.String</param-type>
<param-value>{cache-name}</param-value>
</init-param>
<!—- This param is the fully qualified entity class -->
<init-param>
<param-type>java.lang.String</param-type>
<param-value>com.acme.{cache-name}</param-value>
</init-param>
<!—- This param should match the value of the -->
<!—- persistence unit name in persistence.xml -->
<init-param>
<param-type>java.lang.String</param-type>
<param-value>EmpUnit</param-value>
</init-param>
</init-params>
</class-scheme>
</cachestore-scheme>
</read-write-backing-map-scheme>
</backing-map-scheme>
</distributed-scheme>
</caching-schemes>
</cache-config>
Using Coherence and TopLink Essentials
Introduction
Oracle TopLink is often described as one of the most flexible and scalable object-relational mapping libraries and performs particularly well for read-intensive applications. A streamlined version called TopLink Essentials became the Reference Implementation for the Java Persistence API (JPA) and was open sourced and donated to the Glassfish project at java.net. TopLink Essentials offers all of the essential functionality for O/R mapping through either JPA or the native TopLink API, but it also provides a number of other custom and advanced features for more sophisticated application usage. To obtain a free download of TopLink Essentials go to http://otn.oracle.com/jpa.
Coherence ships a CacheStore implementation that uses TopLink Essentials to load and store objects to the database. This document describes how to configure and use this CacheStore. Note that although this CacheStore allows the objects to be mapped as JPA entities it differs from the JPA CacheStore in that it uses the TopLink runtime API to load and store the objects.
Limitations
Support is currently limited to TopLink Essentials and not Oracle TopLink. In most cases the actual Oracle TopLink mappings used by applications will also work in TopLink Essentials, but the TopLink project mapping files (deployment XML) are not read by TopLink Essentials. TopLink Essentials will read and process TopLink Projects in Java code, though, as well as all types of JPA mappings and metadata.
Conventions
This document refers to the following Java classes and interfaces:
com.tangosol.coherence.toplink.TopLinkCacheLoader
com.tangosol.coherence.toplink.TopLinkCacheStore
com.tangosol.net.NamedCache (extends java.util.Map)
com.tangosol.net.cache.CacheLoader
com.tangosol.net.cache.CacheStore
oracle.toplink.essentials.sessions.Project
oracle.toplink.essentials.threetier.ServerSession
oracle.toplink.essentials.tools.sessionmanagement.SessionManager
As the CacheStore interface extends CacheLoader, the term "CacheStore" will be used generically to refer to both interfaces (the appropriate interface being determined by whether read-only or read-write support is required). Similarly, "TopLinkCacheStore" will refer to both implementations.
The Coherence cache configuration file is referred to as the coherence-cache-config.xml (the default name). TopLink Essentials may be referred to simply as TopLink in this document. The JPA runtime configuration file is referred to as the persistence.xml and the JPA mapping file is referred to as the orm.xml (the default name).
Using the Coherence TopLinkCacheStore
Overview
The TopLink API provides advanced and flexible queries and many relational management features, including referential integrity, cascading deletes and child object fetching. TopLink employs an advanced caching system to properly manage the entities. In many cases the TopLink cache will short-circuit a database operation in order to minimize the operational latency, while in other cases it will simply use its cache to ensure object identity.
Coherence includes a default entity-based CacheStore implementation, TopLinkCacheStore (and a corresponding CacheLoader implementation, TopLinkCacheLoader). Other information may be found in the Javadoc for the implementing classes.
Mapping the Persistent Classes
The first step in being able to load and store objects through the CacheStore is to ensure that the classes are mapped to the database. In TopLink Essentials objects may be mapped using either standard JPA mappings or native TopLink O/R mappings.
JPA mappings are specified either by annotating the entity classes or by adding an orm.xml or other XML mapping files. See the TopLink JPA documentation for more on how to map JPA entities.
TopLink mappings may be used instead of, or in addition to JPA mappings. They may be configured using either a TopLink project class (see the TopLink documentation for more on how to create a Java project class) or a customization class to amend the TopLink descriptors for each class and add the mappings (see the TopLink documentation for more on how to create a customization class and add Java mappings). While a broader set of mappings is available in TopLink the mappings may not be portable to other JPA providers.
Configuring TopLink Essentials
The runtime configuration and startup code will be different depending upon whether JPA mappings or TopLink mappings are used.
Configuration with JPA Mappings
If using JPA mappings then TopLink Essentials is configured using a persistence.xml. Within persistence.xml are the properties that dictate runtime operation. The toplink.session-name property determines the name given to the TopLink session created to model the persistence unit entity manager factory. This property may be set to any non-empty value as long as it is set. Below is a sample persistence.xml showing the toplink.session-name property setting.
<persistence xmlns="http:
xmlns:xsi="http:
xsi:schemaLocation="http: http: version="1.0">
<persistence-unit name="EmpUnit" transaction-type="RESOURCE_LOCAL">
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<class>com.acme.Employee</class>
<properties>
<property name="toplink.jdbc.driver" value="oracle.jdbc.OracleDriver"/>
<property name="toplink.jdbc.url" value="jdbc:oracle:thin:@localhost:1521:XE"/>
<property name="toplink.jdbc.user" value="scott"/>
<property name="toplink.jdbc.password" value="tiger"/>
<property name="toplink.session-name" value="EmployeeSession"/>
</properties>
</persistence-unit>
</persistence>
The transaction type should be set to RESOURCE_LOCAL and the four JDBC properties should contain the appropriate values for connecting and logging in to the database being used. Classes that are mapped using JPA annotations should be listed in <class> elements.
Configuration with TopLink Mappings
When using a Java project class the class may be created either manually or through a tool such as the Mapping Workbench. The class need only be compiled and present on the classpath.
The project class should be instantiated and passed into the session constructor when creating the session. The session must also be added to the SessionManager. The following sample code shows how this might be done.
Project project = new EmployeeMappingProject();
ServerSession session = new ServerSession(project);
SessionManager.getManager().addSession("EmployeeSession", session);
Configuring Coherence
A coherence-cache-config.xml must be specified to override the default Coherence settings and define the TopLinkCacheStore caching scheme. The caching scheme should include a <cachestore-scheme> element that lists the TopLinkCacheStore class and includes two parameters. The first parameter is the entity name, or the alias for the entity being stored. When using JPA this is normally the unqualified name of the entity class, and when mapped using TopLink it is the alias for the class that is set on the descriptor. In the example cache scheme listed below we make use of the built-in Coherence macro {cache-name} that translates to the name of the cache that is constructing and using the cache store. This will work because a separate cache should be used for each type of persistent object and we will ensure that the name of each cache will be set to the name of the entity that is being stored in it.
The second parameter is the name of the session that was indicated by the value of the session-name property in the persistence.xml if using JPA mappings. It is the name that was explicitly given to the session if using a TopLink session directly.
The various named caches are then directed to use the TopLink caching scheme. The following is a sample coherence-cache-config.xml used to define a NamedCache called "Employee" that caches instances of the Employee class. To define additional entity caches for more classes then more <cache-mapping> elements may be added. In this example we are assuming the entities are mapped using JPA mappings.
<cache-config>
<caching-scheme-mapping>
<!—- Configure a named cache -->
<cache-mapping>
<!—- Set the name of the cache to be the entity name -->
<cache-name>Employee</cache-name>
<!—- Configure this cache to use the scheme defined below -->
<scheme-name>toplink-distributed</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<distributed-scheme>
<scheme-name>toplink-distributed</scheme-name>
<service-name>TopLinkDistributedCache</service-name>
<backing-map-scheme>
<read-write-backing-map-scheme>
<internal-cache-scheme>
<local-scheme/>
</internal-cache-scheme>
<!— Define the cache scheme -->
<cachestore-scheme>
<class-scheme>
<class-name>
com.tangosol.coherence.toplink.TopLinkCacheStore
</class-name>
<init-params>
<!—- This param should be the entity name -->
<init-param>
<param-type>java.lang.String</param-type>
<param-value>{cache-name}</param-value>
</init-param>
<!—- This param should match the value of the session-name property -->
<!—- in persistence.xml file if JPA mappings are used, or the name -->
<init-param>
<param-type>java.lang.String</param-type>
<param-value>EmployeeSession</param-value>
</init-param>
</init-params>
</class-scheme>
</cachestore-scheme>
</read-write-backing-map-scheme>
</backing-map-scheme>
<autostart>true</autostart>
</distributed-scheme>
</caching-schemes>
</cache-config>
Using Coherence and Hibernate
Introduction
Hibernate and Coherence can be used together in several combinations. This document discusses the various options, including when and each one is appropriate, along with usage instructions. These options including using Coherence as a Hibernate plug-in, using Hibernate as a Coherence plug-in via the CacheStore interface and bulk-loading Coherence caches from a Hibernate query. Most applications that use Coherence and Hibernate use a mixture of these approaches. The Hibernate API features powerful management of entities and relationships, and the Coherence API delivers maximum performance and scalability.
Conventions
This document refers to the following Java classes and interfaces:
com.tangosol.coherence.hibernate.CoherenceCache
com.tangosol.coherence.hibernate.CoherenceCacheProvider
com.tangosol.coherence.hibernate.HibernateCacheLoader
com.tangosol.coherence.hibernate.HibernateCacheStore
com.tangosol.net.NamedCache (extends java.util.Map)
com.tangosol.net.cache.CacheLoader
com.tangosol.net.cache.CacheStore
org.hibernate.Query
org.hibernate.Session
org.hibernate.SessionFactory
As the CacheStore interface extends CacheLoader, the term "CacheStore" will be used generically to refer to both interfaces (the appropriate interface being determined by whether read-only or read-write support is required). Similarly, "HibernateCacheStore" will refer to both implementations.
The Coherence cache configuration file is referred to as coherence-cache-config.xml (the default name) and the Hibernate root configuration file is referred to as hibernate.cfg.xml (the default name).
Selecting a Caching Strategy
Overview
Generally, the Hibernate API is the optimal choice for accessing data held in a relational database where performance is not the dominant factor. For application state (or any type of data that fits naturally into the Map interface) use the Coherence API. For performance-sensitive operations, specifically those that may benefit from Coherence-specific features like write-behind caching or cache queries, use the Coherence API.
Hibernate API
The Hibernate API provides flexible queries and relational management features including referential integrity, cascading deletes and child object fetching. While these features may be implemented using Coherence, this involves development effort which may not be worthwhile in cases where performance is not an issue.
Coherence NamedCache API
There are many Coherence features that require direct access to the Coherence
NamedCache API, including:
- Write-Behind Caching (low-latency, high-throughput database updates)
- Distributed Queries (low-latency, high-throughput search queries)
- Cache Transactions (application-tier transactions)
- InvocableMap (stored procedures, aggregations)
- Invocation Service (messaging and remote invocation)
- Cache Listeners (event-based processing)
Direct access to these features may be critical for achieving the highest levels of scalable performance.
Coherence CacheStore Integration
CacheStore modules are useful for transparently keeping cache and database synchronized. They are also more efficient than independently updating the cache and database as updates are routed through Coherence's partitioning facilities, minimizing locking.
CacheStore modules give very high performance for caching that can be expressed via a Map interface, that is a key-value pair. The NamedCache interface is a much simpler and by extension much lower-overhead API than the Hibernate query API. Additionally, in some cases (where complex queries can be mapped into a key-based pattern), very complex queries can be answered by a simple cache retrieval.
One final reason for using CacheStore is that it provides a means of coordinating all database (or other backend) access through a single API (NamedCache) and through a controlled set of JVMs (server machines). This is because the nodes which are responsible for managing cache partitions are the same machines responsible for synchronizing with the database server.
Using Coherence as the Hibernate L2 Cache
Introduction
Hibernate supports three primary forms of caching:
- Session cache
- L2 cache
- Query cache
The Session cache is responsible for caching records within a Session (a Hibernate transaction, potentially spanning multiple database transactions, and typically scoped on a per-thread basis). As a non-clustered cache (by definition), the Session cache is managed entirely by Hibernate. The L2 and Query caches span multiple transactions, and support the use of Coherence as a cache provider. The L2 cache is responsible for caching records across multiple sessions (for primary key lookups). The query cache caches the result sets generated by Hibernate queries. Hibernate manages data in an internal representation in the L2 and Query caches, meaning that these caches are usable only by Hibernate. For more details, see the Hibernate Reference Documentation (shipped with Hibernate), specifically the section on the Second Level Cache.
Configuration and Tuning
To use the Coherence Caching Provider for Hibernate, specify the Coherence provider class in the "hibernate.cache.provider_class" property. Typically this is configured in the default Hibernate configuration file, hibernate.cfg.xml.
<property name="hibernate.cache.provider_class">com.tangosol.coherence.hibernate.CoherenceCacheProvider</property>
The file coherence-hibernate.jar (found in the lib/ subdirectory) must be added to the application classpath.
Hibernate provides the configuration property hibernate.cache.use_minimal_puts, which optimizes cache access for clustered caches by increasing cache reads and decreasing cache updates. This is enabled by default by the Coherence Cache Provider. Setting this property to false may increase overhead for cache management and also increase the number of transaction rollbacks.
The Coherence Caching Provider includes a setting for how long a lock acquisition should be attempted before timing out. This may be specified by the Java property tangosol.coherence.hibernate.lockattemptmillis. The default is one minute.
Specifying a Coherence Cache Topology
By default, the Coherence Caching Provider uses a custom cache configuration located in coherence-hibernate.jar named config/hibernate-cache-config.xml to define cache mappings for Hibernate L2 caches. If desired, an alternative cache configuration resource may be specified for Hibernate L2 caches via the tangosol.coherence.hibernate.cacheconfig Java property. It is possible to configure this property to point to the application's main coherence-cache-config.xml file if mappings are properly configured. It may be beneficial to use dedicated cache service(s) to manage Hibernate-specific caches to ensure that any CacheStore modules don't cause re-entrant calls back into Coherence-managed Hibernate L2 caches.
In conjunction with the scheme mapping section of the Coherence cache configuration file, the hibernate.cache.region_prefix property may be used to specify a cache topology. For example, if the cache configuration file includes a wildcard mapping for "near-*", and the Hibernate region prefix property is set to "near-", then all Hibernate caches will be named using the "near-" prefix, and will use the cache scheme mapping specified for the "near-*" cache name pattern.
It is possible to specify a cache topology per entity by creating a cache mapping based on the combined prefix and qualified entity name (e.g. "near-com.company.EntityName"); or equivalently, by providing an empty prefix and specifying a cache mapping for each qualified entity name.
Also, L2 caches should be size-limited to avoid excessive memory usage. Query caches in particular must be size-limited as the Hibernate API does not provide any means of controlling the query cache other than a complete eviction.
Cache Concurrency Strategies
Hibernate generally emphasizes the use of optimistic concurrency for both cache and database. With optimistic concurrency in particular, transaction processing depends on having accurate data available to the application at the beginning of the transaction. If the data is inaccurate, the commit processing will detect that the transaction was dependent on incorrect data, and the transaction will fail to commit. While most optimistic transactions must cope with changes to underlying data by other processes, the use of caching adds the possibility of the cache itself being stale. Hibernate provides a number of cache concurrency strategies to control updates to the L2 cache. While this is less of an issue for Coherence due to support for cluster-wide coherent caches, appropriate selection of cache concurrency strategy will aid application efficiency.
Note that cache configuration strategies may be specified at the table level. Generally, the strategy should be specified in the mapping file for the class.
For mixed read-write activity, the read-write strategy is recommended. The transactional strategy is implemented similarly to the nonstrict-read-write strategy, and relies on the optimistic concurrency features of Hibernate. Note that nonstrict-read-write may deliver better performance if its impact on optimistic concurrency is acceptable.
For read-only caching, use the nonstrict-read-write strategy if the underlying database data may change, but slightly stale data is acceptable. If the underlying database data never changes, use the read-only strategy.
Query Cache
To cache query results, set the hibernate.cache.use_query_cache property to "true". Then whenever issuing a cacheable query, use Query.setCacheable(true) to enable caching of query results. As org.hibernate.cache.QueryKey instances in Hibernate may not be binary-comparable (due to non-deterministic serialization of unordered data members), use a size-limited Local or Replicated cache to store query results (which will force the use of hashcode()/equals() to compare keys). The default query cache name is "org.hibernate.cache.StandardQueryCache" (unless a default region prefix is provided, in which case "[prefix]." will be prepended to the cache name). Use the cache configuration file to map this cache name to a Local/Replicated topology, or explicitly provide an appropriately-mapped region name when querying.
Fault-Tolerance
The Hibernate L2 cache protocol supports full fault-tolerance during client or server failure. With the read-write cache concurrency strategy, Hibernate will lock items out of the cache at the start of an update transaction, meaning that client-side failures will simply result in uncached entities and an uncommitted transaction. Server-side failures are handled transparently by Coherence (dependent on the specified data backup count).
Deployment
When used with application servers that do not have a unified class loader, the Coherence Cache Provider must be deployed as part of the application so that it can use the application-specific class loader (required to serialize-deserialize objects).
Using the Coherence HibernateCacheStore
Overview
Coherence includes a default entity-based CacheStore implementation, HibernateCacheStore (and a corresponding CacheLoader implementation, HibernateCacheLoader). More detailed technical information may be found in the JavaDoc for the implementing classes.
Configuration
The examples below show a simple HibernateCacheStore constructor, accepting only an entity name. This will configure Hibernate using the default configuration path, which looks for a hibernate.cfg.xml file in the classpath. There is also the ability to pass in a resource name or file specification for the hibernate.cfg.xml file as the second <init-param> (set the <param-type> element to java.lang.String for a resource name and java.io.File for a file specification). See the class JavaDoc for more details.
The following is a simple coherence-cache-config.xml file used to define a NamedCache called "TableA" which caches instances of a Hibernate entity (com.company.TableA). To add additional entity caches, add additional <cache-mapping> elements.
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<cache-mapping>
<cache-name>TableA</cache-name>
<scheme-name>distributed-hibernate</scheme-name>
<init-params>
<init-param>
<param-name>entityname</param-name>
<param-value>com.company.TableA</param-value>
</init-param>
</init-params>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<distributed-scheme>
<scheme-name>distributed-hibernate</scheme-name>
<backing-map-scheme>
<read-write-backing-map-scheme>
<internal-cache-scheme>
<local-scheme></local-scheme>
</internal-cache-scheme>
<cachestore-scheme>
<class-scheme>
<class-name>
com.tangosol.coherence.hibernate.HibernateCacheStore
</class-name>
<init-params>
<init-param>
<param-type>java.lang.String</param-type>
<param-value>{entityname}</param-value>
</init-param>
</init-params>
</class-scheme>
</cachestore-scheme>
</read-write-backing-map-scheme>
</backing-map-scheme>
</distributed-scheme>
</caching-schemes>
</cache-config>
It is also possible to use the pre-defined {cache-name} macro to eliminate the need for the <init-params> portion of the cache mapping:
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<cache-mapping>
<cache-name>TableA</cache-name>
<scheme-name>distributed-hibernate</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<distributed-scheme>
<scheme-name>distributed-hibernate</scheme-name>
<backing-map-scheme>
<read-write-backing-map-scheme>
<internal-cache-scheme>
<local-scheme></local-scheme>
</internal-cache-scheme>
<cachestore-scheme>
<class-scheme>
<class-name>
com.tangosol.coherence.hibernate.HibernateCacheStore
</class-name>
<init-params>
<init-param>
<param-type>java.lang.String</param-type>
<param-value>com.company.{cache-name}</param-value>
</init-param>
</init-params>
</class-scheme>
</cachestore-scheme>
</read-write-backing-map-scheme>
</backing-map-scheme>
</distributed-scheme>
</caching-schemes>
</cache-config>
And, if naming conventions allow, the mapping may be completely generalized to allow a cache mapping for any qualified class name (entity name):
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<cache-mapping>
<cache-name>com.company.*</cache-name>
<scheme-name>distributed-hibernate</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<distributed-scheme>
<scheme-name>distributed-hibernate</scheme-name>
<backing-map-scheme>
<read-write-backing-map-scheme>
<internal-cache-scheme>
<local-scheme></local-scheme>
</internal-cache-scheme>
<cachestore-scheme>
<class-scheme>
<class-name>
com.tangosol.coherence.hibernate.HibernateCacheStore
</class-name>
<init-params>
<init-param>
<param-type>java.lang.String</param-type>
<param-value>{cache-name}</param-value>
</init-param>
</init-params>
</class-scheme>
</cachestore-scheme>
</read-write-backing-map-scheme>
</backing-map-scheme>
</distributed-scheme>
</caching-schemes>
</cache-config>
Configuration Requirements
Hibernate entities accessed via the HibernateCacheStore module must use the "assigned" ID generator and also have a defined ID property.
Be sure to disable the "hibernate.hbm2ddl.auto" property in the hibernate.cfg.xml used by the HibernateCacheStore, as this may cause excessive schema updates (and possible lockups).
JDBC Isolation Level
In cases where all access to a database is through Coherence, CacheStore modules will naturally enforce ANSI-style Repeatable Read isolation as reads and writes are executed serially on a per-key basis (via the Partitioned Cache Service). Increasing database isolation above Repeatable Read will not yield increased isolation as CacheStore operations may span multiple Partitioned Cache nodes (and thus multiple database transactions). Using database isolation levels below Repeatable Read will not result in unexpected anomalies, and may reduce processing load on the database server.
Fault-Tolerance
For single-cache-entry updates, CacheStore operations are fully fault-tolerant in that the cache and database are guaranteed to be consistent during any server failure (including failures during partial updates). While the mechanisms for fault-tolerance vary, this is true for both write-through and write-behind caches.
Coherence does not support two-phase CacheStore operations across multiple CacheStore instances. In other words, if two cache entries are updated, triggering calls to CacheStore modules sitting on separate servers, it is possible for one database update to succeed and for the other to fail. In this case, it may be preferable to use a cache-aside architecture (updating the cache and database as two separate components of a single transaction) in conjunction with the application server transaction manager. In many cases it is possible to design the database schema to prevent logical commit failures (but obviously not server failures). Write-behind caching avoids this issue as "puts" are not affected by database behavior (and the underlying issues will have been addressed earlier in the design process).
Extending HibernateCacheStore
In some cases, it may be desired to extend the HibernateCacheStore with application-specific functionality. The most obvious reason for this is to leverage a pre-existing programmatically-configured SessionFactory instance.
Creating a Hibernate CacheStore
Introduction
While the provided HibernateCacheStore module provides a solution for most entity-based caches, there may be cases where an application-specific CacheStore module is necessary. For example, providing parameterized queries or including or post-processing of query results.
Re-entrant Calls
In a CacheStore-backed cache implementation, when the application thread accesses cached data, the cache operations may trigger a call to the associated CacheStore implementation via the managing CacheService. The CacheStore must not call back into the CacheService API. This implies, indirectly, that Hibernate should not attempt to access cache data. Therefore, all methods in CacheLoader/CacheStore should be careful to call Session.setCacheMode(CacheMode.IGNORE) to disable cache access. Alternatively, the Hibernate configuration may be cloned (either programmatically or via hibernate.cfg.xml), with CacheStore implementations using the version with the cache disabled.
It is important that a CacheStore implementation does not call back into the hosting cache service. Therefore, in addition to avoiding calls to NamedCache methods, you should also ensure that Hibernate itself does not use any cache services. To do this, call Session.setCacheMode(CacheMode.IGNORE) each time a session is used. Alternatively, the Hibernate configuration may be cloned (either programmatically or via hibernate.cfg.xml), with CacheStore implementations using the version with the cache disabled.
Fully Cached DataSets
Distributed Queries
Distributed queries offer the potential for lower latency, higher throughput and less database server load compared to executing queries on the database server. For set-oriented queries, the dataset must be entirely cached to produce correct query results. More precisely, for a query issued against the cache to produce correct results, the query must not depend on any uncached data.
This means that you can create hybrid caches. For example, it is possible to combine two uses of a NamedCache: a fully cached size-limited dataset for querying (e.g. the data for the most recent week), and a partially cached historical dataset used for singleton reads. This is a good approach to avoid data duplication and minimize memory usage.
While fully cached datasets are usually bulk-loaded during application startup (or on a periodic basis), CacheStore integration may be used to ensure that both cache and database are kept fully synchronized.
Detached Processing
Another reason for using fully-cached datasets is to provide the ability to continue application processing even if the underlying database goes down. Using write-behind caching extends this mode of operation to support full read-write applications. With write-behind, the cache becomes (in effect) the temporary system of record. Should the database fail, updates will be queued in Coherence until the connection is restored, at which point all cache changes will be sent to the database.
Architecture
Overview for Implementors
Basic Concepts
Audience
This document is targeted at software developers and architects who need a very quick overview of Coherence features. This document outlines product capabilities, usage possibilities, and provides a brief overview of how one would go about implementing particular features. This document bridges the more abstract Coherence white-papers and the more concrete developer documentation (Oracle Coherence User Guide and the Coherence API documentation).
Clustered Data Management
At the core of Coherence is the concept of clustered data management. This implies the following goals:
- A fully coherent, single system image (SSI)
- Scalability for both read and write access
- Fast, transparent failover and failback
- Linear scalability for storage and processing
- No Single-Points-of-Failure (SPOFs)
- Cluster-wide locking and transactions
Built on top of this foundation are the various services that Coherence provides, including database caching, HTTP session management, grid agent invocation and distributed queries. Before going into detail about these features, some basic aspects of Coherence should be discussed.
A single API for the logical layer, XML configuration for the physical layer
Coherence supports many topologies for clustered data management. Each of these topologies has trade-offs in terms of performance and fault-tolerance. By using a single API, the choice of topology can be deferred until deployment if desired. This allows developers to work with a consistent logical view of Coherence, while providing flexibility during tuning or as application needs change.
Caching Strategies
Coherence provides several cache implementations:
- Local - Local on-heap caching for non-clustered caching.
- Replicated - Perfect for small, read-heavy caches.
- Partitioned - True linear scalability for both read and write access. Data is automatically, dynamically and transparently partitioned across nodes. The distribution algorithm minimizes network traffic and avoids service pauses by incrementally shifting data.
- Near Cache - Provides the performance of local caching with the scalability of distributed caching. Several different near-cache strategies provide varying tradeoffs between performance and synchronization guarantees.
In-process caching provides the highest level of raw performance, since objects are managed within the local JVM. This benefit is most directly realized by the Local, Replicated, Optimistic and Near Cache implementations.
Out-of-process (client-server) caching provides the option of using dedicated cache servers. This can be helpful when you wish to partition workloads (to avoid stressing the application servers). This is accomplished by using the Partitioned cache implementation and simply disabling local storage on client nodes via a single command-line option or a one-line entry in the XML configuration.
Tiered caching (using the NearCache functionality) allows you to couple local caches on the application server with larger, partitioned caches on the cache servers, combining the raw performance of local caching with the scalability of partitioned caching. This is useful for both dedicated cache servers as well as co-located caching (cache partitions stored within the application server JVMs).
Data Storage Options
While most customers use on-heap storage combined with dedicated cache servers, Coherence has several options for data storage:
- On-heap - The fastest option, though it can affect JVM garbage collection times.
- NIO RAM - No impact on garbage collection, though it does require serialization/deserialization.
- NIO Disk - Similar to NIO RAM, but using memory-mapped files.
- File-based - Uses a special disk-optimized storage system to optimize speed and minimize I/O.
It should be noted that Coherence storage is transient – the disk-based storage options are for managing cached data only. If long-term persistence of data is required, Coherence provides snapshot functionality to persist an image to disk. This is especially useful when the external data sources used to build the cache are extremely expensive. By using the snapshot, the cache can be rebuilt from an image file (rather than reloading from a very slow external datasource).
Serialization Options
Because serialization is often the most expensive part of clustered data management, Coherence provides three options for serializing/deserializing data:
- java.io.Serializable - The simplest, but slowest option.
- java.io.Externalizable - This requires developers to implement serialization manually, but can provide significant performance benefits. Compared to java.io.Serializable, this can cut serialized data size by a factor of two or more (especially helpful with Distributed caches, as they generally cache data in serialized form). Most importantly, CPU usage is dramatically reduced.
- com.tangosol.io.ExternalizableLite - This is very similar to java.io.Externalizable, but offers better performance and less memory usage by using a more efficient I/O stream implementation. The
com.tangosol.run.xml.XmlBean class provides a default implementation of this interface (see the section on XmlBean for more details).
Configurability and Extensibility
Coherence's API provides access to all Coherence functionality. The most commonly used subset of this API is exposed via simple XML options to minimize effort for typical use cases. There is no penalty for mixing direct configuration via the API with the easier XML configuration.
Coherence is designed to allow the replacement of its modules as needed. For example, the local "backing maps" (which provide the actual physical data storage on each node) can be easily replaced as needed. The vast majority of the time, this is not required, but it is there for the situations that require it. The general guideline is that 80% of tasks are easy, and the remaining 20% of tasks (the special cases) require a little more effort, but certainly can be done without significant hardship.
Namespace Hierarchy
Coherence is organized as set of services. At the root is the "Cluster" service. A cluster is defined as a set of Coherence instances (one instance per JVM, with one or more JVMs on each physical machine). A cluster is defined by the combination of multicast address and port. A TTL (network packet time-to-live; i.e., the number of network hops) setting can be used to restrict the cluster to a single machine, or the machines attached to a single switch.
Under the cluster service are the various services that comprise the Coherence API. These include the various caching services (Replicated, Distributed, etc.) as well as the Invocation Service (for deploying agents to various nodes of the cluster). Each instance of a service is named, and there is typically a default service instance for each type.
The cache services contain NamedCaches (
com.tangosol.net.NamedCache), which are analogous to database tables – that is, they typically contain a set of related objects.
Read/Write Caching
NamedCache
The following source code will return a reference to a NamedCache instance. The underlying cache service will be started if necessary.
import com.tangosol.net.*;
...
NamedCache cache = CacheFactory.getCache("MyCache");
Coherence will scan the cache configuration XML file for a name mapping for MyCache. NamedCache name mapping is similar to Servlet name mapping in a web container's web.xml file. Coherence's cache configuration file contains (in the simplest case) a set of mappings (from cache name to cache strategy) and a set of cache strategies.
By default, Coherence will use the coherence-cache-config.xml file found at the root of coherence.jar. This can be overridden on the JVM command-line with -Dtangosol.coherence.cacheconfig=file.xml. This argument can reference either a file system path, or a Java resource path.
The com.tangosol.net.NamedCache interface extends a number of other interfaces:
- java.util.Map - basic Map methods such as get(), put(), remove().
- com.tangosol.util.QueryMap - methods for querying the cache.
- com.tangosol.util.ConcurrentMap - methods for concurrent access such as lock() and unlock().
- com.tangosol.util.ObservableMap - methods for listening to cache events.
- com.tangosol.util.InvocableMap - methods for server-side processing of cache data.
 | Requirements for cached objects
Cache keys and values must be serializable (e.g. java.io.Serializable). Furthermore, cache keys must provide an implementation of the hashCode() and equals() methods, and those methods must return consistent results across cluster nodes. This implies that the implementation of hashCode() and equals() must be based solely on the object's serializable state (i.e. the object's non-transient fields); most built-in Java types, such as String, Integer and Date, meet this requirement. Some cache implementations (specifically the partitioned cache) use the serialized form of the key objects for equality testing, which means that keys for which equals() returns true must serialize identically; most built-in Java types meet this requirement as well. |
NamedCache Usage Patterns
There are two general approaches to using a NamedCache:
- As a clustered implementation of java.util.Map with a number of added features (queries, concurrency), but with no persistent backing (a "side" cache).
- As a means of decoupling access to external data sources (an "inline" cache). In this case, the application uses the NamedCache interface, and the NamedCache takes care of managing the underlying database (or other resource).
Typically, an inline cache is used to cache data from:
- a database - The most intuitive use of a cache – simply caching database tables (in the form of Java objects).
- a service - Mainframe, web service, service bureau – any service that represents an expensive resource to access (either due to computational cost or actual access fees).
- calculations - Financial calculations, aggregations, data transformations. Using an inline cache makes it very easy to avoid duplicating calculations. If the calculation is already complete, the result is simply pulled from the cache. Since any serializable object can be used as a cache key, it's a simple matter to use an object containing calculation parameters as the cache key.
Write-back options:
- write-through - Ensures that the external data source always contains up-to-date information. Used when data must be persisted immediately, or when sharing a data source with other applications.
- write-behind - Provides better performance by caching writes to the external data source. Not only can writes be buffered to even out the load on the data source, but multiple writes can be combined, further reducing I/O. The trade-off is that data is not immediately persisted to disk; however, it is immediately distributed across the cluster, so the data will survive the loss of a server. Furthermore, if the entire data set is cached, this option means that the application can survive a complete failure of the data source temporarily as both cache reads and writes do not require synchronous access the the data source.
To implement a read-only inline cache, you simply implement two methods on the
com.tangosol.net.cache.CacheLoader interface, one for singleton reads, the other for bulk reads. Coherence provides an abstract class
com.tangosol.net.cache.AbstractCacheLoader which provides a default implementation of the bulk method, which means that you need only implement a single method: public Object load(Object oKey);
This method accepts an arbitrary cache key and returns the appropriate value object.
If you want to implement read-write caching, you need to extend
com.tangosol.net.cache.AbstractCacheStore (or implement the interface
com.tangosol.net.cache.CacheStore), which adds the following methods:
public void erase(Object oKey);
public void store(Object oKey, Object oValue);
The method erase() should remove the specified key from the external data source. The method store() should update the specified item in the data source if it already exists, or insert it if it does not presently exist.
Once the CacheLoader/CacheStore is implemented, it can be connected via the coherence-cache-config.xml file.
Querying the Cache
Coherence provides the ability to query cached data. With partitioned caches, the queries are indexed and parallelized. This means that adding servers to a partitioned cache not only increases throughput (total queries per second) but also reduces latency, with queries taking less user time. To query against a NamedCache, all objects should implement a common interface (or base class). Any field of an object can be queried; indexes are optional, and used to increase performance. With a replicated cache, queries are performed locally, and do not use indexes.
To add an index to a NamedCache, you first need a value extractor (which accepts as input a value object and returns an attribute of that object). Indexes can be added blindly (duplicate indexes are ignored). Indexes can be added at any time, before or after inserting data into the cache.
It should be noted that queries apply only to cached data. For this reason, queries should not be used unless the entire data set has been loaded into the cache, unless additional support is added to manage partially loaded sets.
Developers have the option of implementing additional custom filters for queries, thus taking advantage of query parallelization. For particularly performance-sensitive queries, developers may implement index-aware filters, which can access Coherence's internal indexing structures.
Coherence includes a built-in optimizer, and will apply indexes in the optimal order. Because of the focused nature of the queries, the optimizer is both effective and efficient. No maintenance is required.
Example code to create an index:
NamedCache cache = CacheFactory.getCache("MyCache");
ValueExtractor extractor = new ReflectionExtractor("getAttribute");
cache.addIndex(extractor, true, null);
Example code to query a NamedCache (returns the keys corresponding to all of the value objects with an "Attribute" greater than 5):
NamedCache cache = CacheFactory.getCache("MyCache");
Filter filter = new GreaterFilter("getAttribute", 5);
Set keySet = cache.keySet(filter);
Transactions
Coherence supports local transactions against the cache through both a direct API, as well as through J2CA adapters for J2EE containers. Transactions support either pessimistic or optimistic concurrency strategies, as well as the Read Committed, Repeatable Read, Serializable isolation levels.
HTTP Session Management
Coherence*Web is an HTTP session-management module with support for a wide range of application servers. Using Coherence session management does not require any changes to the application.
Coherence*Web uses the NearCache technology to provide fully fault-tolerant caching, with almost unlimited scalability (to several hundred cluster nodes without issue).
Invocation Service
The Coherence Invocation service can be used to deploy computational agents to various nodes within the cluster. These agents can be either execute-style (deploy and asynchronously listen) or query-style (deploy and wait for results).
The Invocation service is accessed through the interface
com.tangosol.net.InvocationService through the two following methods:
public void execute(Invocable task, Set setMembers, InvocationObserver observer);
public Map query(Invocable task, Set setMembers);
An instance of the service can be retrieved from the
com.tangosol.net.CacheFactory class.
Coherence implements the
WorkManager API for task-centric processing.
Events
All NamedCache instances in Coherence implement the com.tangosol.util.ObservableMap interface, which allows the option of attaching a cache listener implementation (of com.tangosol.util.MapListener). It should be noted that applications can observe events as logical concepts regardless of which physical machine caused the event. Customizable server-side filters and lightweight events can be used to minimize network traffic and processing. Cache listeners follow the JavaBean paradigm, and can distinguish between system cache events (e.g., eviction) and application cache events (e.g., get/put operations).
Continuous Query functionality provides the ability to maintain a client-side "materialized view".
Similarly, any service can be watched for members joining and leaving, including the cluster service as well as the cache and invocation services.
Object-Relational Mapping Integration
Most ORM products support Coherence as an "L2" caching plug-in. These solutions cache entity data inside Coherence, allowing application on multiple servers to share cached data.
C++/.NET Integration
Traditionally, customers wishing to access Coherence from C++ applications and .NET applications have used integration technologies from CodeMesh and JNBridge. This integration takes the form of local proxy objects that control remote Coherence objects within the JVM.
In the interest of streamlining multi-platform development, Coherence 3.2 introduced support for any cross-platform client that adheres to the Coherence*Extend wire protocol. Coherence 3.2 includes a Java client implementation; subsequent releases are scheduled to add support for .NET and C++ clients.
Management and Monitoring
Coherence offers management and monitoring facilities via Java Management Extensions (JMX). A detailed set of statistics and commands is maintained in the API documentation for
com.tangosol.net.management.Registry.
Managing an Object Model
Overview
This document describes best practices for managing an object model whose state is managed in a collection of Coherence named caches. Given a set of entity classes, and a set of entity relationships, what is the best means of expressing and managing the object model across a set of Coherence named caches?
Cache Usage Paradigms
The value of a clustered caching solution depends on how it is used. Is it simply caching database data in the application tier, keeping it ready for instant access? Is it taking the next step to move transactional control into the application tier? Or does it go a step further by aggressively optimizing transactional control?
Simple Data Caching
Simple data caches are common, especially in situations where concurrency control is not required (e.g. content caching) or in situations where transactional control is still managed by the database (e.g. plug-in caches for Hibernate and JDO products). This approach has minimal impact on application design, and is often implemented transparently by the Object/Relational Mapping (ORM) layer or the application server (EJB container, Spring, etc). However, it still does not completely solve the issue of overloading the database server; in particular, while non-transactional reads are handled in the application tier, all transactional data management still requires interaction with the database.
It is important to note that caching is not an orthogonal concern once data access requirements go beyond simple access by primary key. In other words, to truly benefit from caching, applications must be designed with caching in mind.
Transactional Caching
Applications that need to scale and operate independently of the database must start to take greater responsibility for data management. This includes using Coherence features for read access (read-through caching, cache queries, aggregations), features for minimizing database transactions (write-behind), and features for managing concurrency (locking, cache transactions).
Transaction Optimization
Applications that need to combine fault-tolerance, low latency and high scalability will generally need to optimize transactions even further. Using traditional transaction control, an application might need to specify SERIALIZABLE isolation when managing an Order object. In a distributed environment, this can be a very expensive operation. Even in non-distributed environments, most databases and caching products will often use a table lock to achieve this. This places a hard limit on scalability regardless of available hardware resources; in practice, this may limit transaction rate to hundreds of transactions per second, even with exotic hardware. However, locking "by convention" can help – for example, requiring that all acessors lock only the "parent" Order object. Doing this can reduce the scope of the lock from table-level to order-level, enabling far higher scalability. (Of course, some applications already achieve similar results by partitioning event processing across multiple JMS queues to avoid the need for explicit concurrency control.) Further optimizations include using an EntryProcessor to avoid the need for clustered coordination, which can dramatically increase the transaction rate for a given cache entry.
Managing the Object Model
Overview
The term "relationships" refers to how objects are related to each other. For example, an Order object may contain (exclusively) a set of LineItem objects. It may also refer to a Customer object that is associated with the Order object.
The data access layer can generally be broken down into two key components, Data Access Objects (DAOs) and Data Transfer Objects (DTOs) (in other words, behavior and state). DAOs control the behavior of data access, and generally will contain the logic that manages the database or cache. DTOs contain data for use with DAOs, for example, an Order record. Also, note that a single object may (in some applications) act as both a DTO and DAO. These terms describe patterns of usage; these patterns will vary between applications, but the core principles will be applicable. For simplicity, the examples in this document follow a "Combined DAO/DTO" approach (behaviorally-rich Object Model).
Managing entity relationships can be a challenging task, especially when scalability and transactionality are required. The core challenge is that the ideal solution must be capable of managing the complexity of these inter-entity relationships with a minimum of developer involvement. Conceptually, the problem is one of taking the relationship model (which could be represented in any of a number of forms, including XML or Java source) and providing runtime behavior which adheres to that description.
Present solutions can be categorized into a few groups:
- Code generation (.java or .class files)
- Runtime byte-code instrumentation (ClassLoader interception)
- Predefined DAO methods
Code Generation
Code generation is a popular option, involving the generation of .java or .class files. This approach is commonly used with a number of Management and Monitoring, AOP and ORM tools (AspectJ, Hibernate). The primary challenges with this approach are the generation of artifacts, which may need to be managed in a software configuration management (SCM) system.
Byte-code instrumentation
This approach uses ClassLoader interception to instrument classes as they are loaded into the JVM. This approach is commonly used with AOP tools (AspectJ, JBossCacheAop, TerraCotta) and some ORM tools (very common with JDO implementations). Due to the risks (perceived or actual) associated with run-time modification of code (including a tendency to break the hot-deploy options on application servers), this option isn't viable for many organizations and as such is a non-starter.
Developer-implemented classes
The most flexible option is to have a runtime query engine. ORM products shift most of this processing off onto the database server. The alternative is to provide the query engine inside the application tier; but again, this leads toward the same complexity that limits the manageability and scalability of a full-fledged database server.
The recommended practice for Coherence is to map out DAO methods explicitly. This provides deterministic behavior (avoiding dynamically evaluated queries), with some added effort of development. This effort will be directly proportional to the complexity of the relationship model. For small- to mid-size models (up to ~50 entity types managed by Coherence), this will be fairly modest development effort. For larger models (or for those with particularly complicated relationships), this may be a substantial development effort.
As a best practice, all state, relationships and atomic transactions should be handled by the Object Model. For more advanced transactional control, there should be an additional Service Layer which coordinates concurrency (allowing for composable transactions).
 | Composable Transactions
The Java language does not directly support composable transactions (the ability to combine multiple transactions into a single transaction). The core Coherence API, based on standard JavaSE patterns, does not either. Locking and synchronization operations support only non-composable transactions.
To quote from the Microsoft Research Paper titled "Composable Memory Transactions (PDF)":
Perhaps the most fundamental objection, though, is that lock-based programs do not compose: correct fragments may fail when combined. For example, consider a hash table with thread-safe insert and delete operations. Now suppose that we want to delete one item A from table t1, and insert it into table t2; but the intermediate state (in which neither table contains the item) must not be visible to other threads. Unless the implementor of the hash table anticipates this need, there is simply no way to satisfy this requirement. [...] In short, operations that are individually correct (insert, delete) cannot be composed into larger correct operations.
—Tim Harris et al, "Composable Memory Transactions", Section 2
This is one reason that "transparent clustering" (or any form of clustering that relies on the basic Java language features) only works for non-composable transactions. Applications may use the (composable)
TransactionMap to enable composite transactions. Alternatively, applications may use transactional algorithms such as ordered locking to more efficiently support desired levels of isolation and atomicity. The "Service Layer" section below will go into more detail on the latter. |
Domain Model
Basics
A NamedCache should contain one type of entity (in the same way that a database table contains one type of entity). The only common exception to this are directory-type caches, which often may contain arbitrary values.
Each additional NamedCache consumes only a few dozen bytes of memory per participating cluster member. This will vary, based on the backing map. Caches configured with the <read-write-backing-map-scheme> for transparent database integration will consume additional resources if write-behind caching is enabled, but this will not be a factor until there are hundreds of named caches.
If possible, cache layouts should be designed so that business transactions map to a single cache entry update. This simplifies transactional control and can result in much greater throughput.
Most caches should use meaningful keys (as opposed to the "meaningless keys" commonly used in relational systems whose only purpose is to manage identity). The one drawback to this is limited query support (as Coherence queries at present apply only to the entry value, not the entry key); to query against key attributes, the value must duplicate the attributes.
Coherence Best Practices
DAO objects must implement the getter/setter/query methods in terms of NamedCache access. The NamedCache API makes this very simple for the most types of operations, especially primary key lookups and simple search queries.
public class Order
implements Serializable
{
public static Order getOrder(OrderId orderId)
{
return (Order)m_cacheOrders.get(orderId);
}
public Customer getCustomer()
{
return (Customer)m_cacheCustomers.get(m_customerId);
}
public Collection getLineItems()
{
return ((Map)m_cacheLineItems.getAll(m_lineItemIds)).values();
}
private CustomerId m_customerId;
private Collection lineItemIds;
private static final NamedCache m_cacheCustomers = CacheFactory.getCache("customers");
private static final NamedCache m_cacheOrders = CacheFactory.getCache("orders");
private static final NamedCache m_cacheLineItems = CacheFactory.getCache("orderlineitems");
}
Service Layer
Overview
Applications that require composite transactions should use a Service Layer. This accomplishes two things. First, it allows for proper composing of multiple entities into a single transaction without compromising ACID characteristics. Second, it provides a central point of concurrency control, allowing aggressively optimized transaction management.
Automatic Transaction Management
Basic transaction management consists of ensuring clean reads (based on the isolation level) and consistent, atomic updates (based on the concurrency strategy). The TransactionMap API (accessible either through the J2CA adapter or programmatically) will handle these issues automatically.
Explicit Transaction Management
Unfortunately, the transaction characteristics common with database transactions (described as a combination of isolation level and concurrency strategy for the entire transaction) provide very coarse-grained control. This coarse-grained control is often unsuitable for caches, which are generally subject to far greater transaction rates. By manually controlling transactions, applications can gain much greater control over concurrency and therefore dramatically increase efficiency.
The general pattern for pessimistic transactions is "lock -> read -> write -> unlock". For optimistic transactions, the sequence is "read -> lock & validate -> write -> unlock". When considering a two-phase commit, "locking" is the first phase, and "writing" is the second phase. Locking individual objects will ensure REPEATABLE_READ isolation semantics. Dropping the locks will be equivalent to READ_COMMITTED isolation.
By mixing isolation and concurrency strategies, applications can achieve higher transaction rates. For example, an overly pessimistic concurrency strategy will reduce concurrency, but an overly optimistic strategy may cause excessive transaction rollbacks. By intelligently deciding which entities will be managed pessimistically, and which optimistically, applications can balance the tradeoffs. Similarly, many transactions may require strong isolation for some entities, but much weaker isolation for other entities. Using only the necessary degree of isolation can minimize contention, and thus improve processing throughput.
Optimized Transaction Processing
There are a number of advanced transaction processing techniques that can best be applied in the Service Layer. Proper use of these techniques can dramatically improve throughput, latency and fault-tolerance, at the expense of some added effort.
The most common solution relates to minimizing the need for locking. Specifically, using an ordered locking algorithm can reduce the number of locks required, and also eliminate the possibility of deadlock. The most common example is to lock a parent object prior to locking child object. In some cases, the Service Layer can depend on locks against the parent object to protect the child objects. This effectively makes locks coarse-grained (slightly increasing contention) and substantially minimizes the lock count.
public class OrderService
{
public void executeOrderIfLiabilityAcceptable(Order order)
{
OrderId orderId = order.getId();
m_cacheOrders.lock(orderId, -1);
try
{
BigDecimal outstanding = new BigDecimal(0);
Collection lineItems = order.getLineItems();
for (Iterator iter = lineItems.iterator(); iter.hasNext(); )
{
LineItem item = (LineItem)iter.next();
outstanding = outstanding.add(item.getAmount());
}
Customer customer = order.getCustomer();
if (customer.isAcceptableOrderSize(outstanding))
{
order.setStatus(Order.REJECTED);
}
else
{
order.setStatus(Order.EXECUTED);
}
m_cacheOrders.put(order);
}
finally
{
m_cacheOrders.unlock(orderId);
}
}
}
Relationship Patterns
Managing Collections of Child Objects
Shared Child Objects
For shared child objects (e.g. two parent objects may both refer to the same child object), the best practice is to maintain a list of child object identifiers (aka foreign keys) in the parent object. Then use the NamedCache.get() or NamedCache.getAll() methods to access the child objects. In many cases, it may make sense to use a Near cache for the parent objects and a Replicated cache for the referenced objects (especially if they are read-mostly or read-only).
If the child objects are read-only (or stale data is acceptable), and the entire object graph is often required, then including the child objects in the parent object may be beneficial in reducing the number of cache requests. This is less likely to make sense if the referenced objects are already local, as in a Replicated, or in some cases, Near cache, as local cache requests are very efficient. Also, this makes less sense if the child objects are large. On the other hand, if fetching the child objects from another cache is likely to result in additional network operations, the reduced latency of fetching the entire object graph at once may outweigh the cost of inlining the child objects inside the parent object.
Owned Child Objects
If the objects are owned exclusively, then there are a few additional options. Specifically, it is possible to manage the object graph "top-down" (the normal approach), "bottom-up", or both. Generally, managing "top-down" is the simplest and most efficient approach.
If the child objects are inserted into the cache before the parent object is updated (an "ordered update" pattern), and deleted after the parent object's child list is updated, the application will never see missing child objects.
Similarly, if all Service Layer access to child objects locks the parent object first, SERIALIZABLE-style isolation can be provided very inexpensively (with respect to the child objects).
Bottom-Up Management of Child Objects
To manage the child dependencies "bottom-up", tag each child with the parent identifier. Then use a query (semantically, "find children where parent = ?") to find the child objects (and then modify them if needed). Note that queries, while very fast, are slower than primary key access. The main advantage to this approach is that it reduces contention for the parent object (within the limitations of READ_COMMITTED isolation). Of course, efficient management of a parent-child hierarchy could also be achieved by combining the parent and child objects into a single composite object, and using a custom "Update Child" EntryProcessor, which would be capable of hundreds of updates per second against each composite object.
Bi-Directional Management of Child Objects
Another option is to manage parent-child relationships bi-directionally. An advantage to this is that each child "knows" about its parent, and the parent "knows" about the child objects, simplifying graph navigation (e.g. allowing a child object to find its sibling objects). The biggest drawback is that the relationship state is redundant; for a given parent-child relationship, there is data in both the parent and child objects. This complicates ensuring resilient, atomic updates of the relationship information and makes transaction management more difficult. It also complicates ordered locking/update optimizations.
Colocating Owned Objects
Denormalization
Exclusively owned objects may be managed as normal relationships (wrapping getters/setters around NamedCache methods), or the objects may be embedded directly (roughly analogous to "denormalizing" in database terms). Note that by denormalizing, data is not being stored redundantly, only in a less flexible format. However, since the cache schema is part of the application, and not a persistent component, the loss of flexibility is a non-issue as long as there is not a requirement for efficient ad hoc querying. Using an application-tier cache allows for the cache schema to be aggressively optimized for efficiency, while allowing the persistent (database) schema to be flexible and robust (typically at the expense of some efficiency).
The decision to inline child objects is dependent on the anticipated access patterns against the parent and child objects. If the bulk of cache accesses are against the entire object graph (or a substantial portion thereof), it may be optimal to embed the child objects (optimizing the "common path").
To optimize access against a portion of the object graph (e.g. retrieving a single child object, or updating an attribute of the parent object), use an EntryProcessor to move as much processing to the server as possible, sending only the required data across the network.
Affinity
Partition Affinity can be used to optimize colocation of parent and child objects (ensuring that the entire object graph is always located within a single JVM). This will minimize the number of servers involved in processing a multiple-entity request (queries, bulk operations, etc.). Affinity offers much of the benefit of denormalization without having any impact on application design. However, denormalizing structures can further streamline processing (e.g., turning graph traversal into a single network operation).
Managing Shared Objects
Shared objects should be referenced via a typical "lazy getter" pattern. For read-only data, the returned object may be cached in a transient (non-serializable) field for subsequent access.
public class Order
{
public Customer getCustomer()
{
return (Customer)m_cacheCustomers.get(m_customerId);
}
}
As usual, multiple-entity updates (e.g. updating both the parent and a child object) should be managed by the service layer.
Refactoring Existing DAOs
Generally, when refactoring existing DAOs, the pattern is to split the existing DAO into an interface and two implementations (the original database logic and the new cache-aware logic). The application will continue to use the (extracted) interface. The database logic will be moved into a CacheStore module. The cache-aware DAO will access the NamedCache (backed by the database DAO). All DAO operations that can't be mapped onto a NamedCache will be passed directly to the database DAO.

All trademarks and registered trademarks are the property of their respective owners.
Getting Started
Introduction
Overview
This document is targeted at software developers and architects. This document provides detailed technical information for installing, configuring, developing with, and finally deploying Oracle Coherence.
For ease-of-reading, this document uses only the most basic formatting conventions. Code elements and file contents are printed with a fixed-width font. Multi-line code segments are also color-coded for easier reading.
Oracle Coherence is a JCache-compliant in-memory caching and data management solution for clustered J2EE applications and application servers. Coherence makes sharing and managing data in a cluster as simple as on a single server. It accomplishes this by coordinating updates to the data using cluster-wide concurrency control, replicating and distributing data modifications across the cluster using the highest performing clustered protocol available, and delivering notifications of data modifications to any servers that request them. Developers can easily take advantage of Coherence features using the standard Java collections API to access and modify data, and use the standard JavaBean event model to receive data change notifications. Functionality such as HTTP Session Management is available out-of-the-box for applications deployed to WebLogic, WebSphere, Tomcat, Jetty and other Servlet 2.2, 2.3 and 2.3 compliant application servers.
Terms
Overview
There are several terms which are used to describe the ability of multiple servers to work together to handle additional load or to survive the failure of a particular server:
- Failback
Failback is an extension to failover that allows a server to reclaim its responsibilities once it restarts. For example, "When the server came back up, the processes that it was running previously were failed back to it."
- Failover
Failover refers to the ability of a server to assume the responsibilities of a failed server. For example, "When the server died, its processes failed over to the backup server."
- Federated Server Model
A federated server model allows multiple servers to cooperate in a distributed manner as if they were a single server, while delegating responsibilities to certain specific servers within the federation. For example, in a federated database, servers from different database vendors can appear to an application to be a single server.
- Load Balancer
A load balancer is a hardware device or software program that delegates network requests to a number of servers, such as in a server farm or server cluster. Load balancers typically can detect server failure and optionally retry operations that were being processed by that server at the time of its failure. Load balancers typically attempt to keep the servers to which they delegate equally busy, hence the use of the term "balancer". Load balancer devices often have a high-availability option that uses a second load balancer, allowing one of the load balancer devices to die without affecting availability.
- Server Cluster
A server cluster is composed of multiple servers that are each aware of the other servers in the cluster, can directly communicate with each other, share responsibilities (load-balance), and are able to assume the responsibilities failed servers. Generally speaking, clustering usually implies a concept of shared resources or shared state.
- Server Farm
A server farm utilizes multiple servers to handle increased load and provide increased availability. It is common for a load-balancer to be used to assign work to the various servers in the server farm, and server farms often share back-end resources, such as database servers, but each server is typically unaware of other servers in the farm, and usually the load-balancer is responsible for failover.
- JCache
JCache (also known as JSR-107), is a caching API specification that is currently in progress. While the final version of this API has not been released yet, Oracle and other companies with caching products have been tracking the current status of the API. The API has been largely solidified at this point. Few significant changes are expected going forward.
It is worth noting that the terms federated server model, server clustering and server farming are often used loosely and interchangeably.
Oracle Coherence supports both homogenous server clusters and the federated server model. Any application or server process that is running the Coherence software is called a cluster node. All cluster nodes on the same network will automatically cluster together . Cluster nodes use a peer-to-peer protocol, which means that any cluster node can talk directly to any other cluster node.
Coherence is logically sub-divided into clusters, services and caches. A Coherence cluster is a group of cluster nodes that share a group address, which allows the cluster nodes to communicate. Generally, a cluster node will only join one cluster, but it is possible for a cluster node to join (be a member of) several different clusters, by using a different group address for each cluster.
Within a cluster, there exists any number of named services. A cluster node can participate in (join) any number of these services; when a cluster node joins a service, it automatically has all of the information from that service available to it; for example, if the service is a replicated cache service, then joining the service includes replicating the data of all the caches in the service. These services are all peer-to-peer, which means that a cluster node typically plays both the client and the server role through the service; furthermore, all of these services will failover in the event of cluster node failure without any data loss.
Installing Oracle Coherence
Downloading and Extracting Coherence
For Windows and any other OS supporting the .zip format, Coherence is downloadable as a .zip file. If prompted by your browser, choose to save the downloaded file. Once it has completed downloading, expand the .zip file (using WinZip or the unzip command-line utility) to the location of your choice. On Windows, you can expand it to your c:\ directory; on Unix, it is suggested that you expand it to the /opt directory. Expanding the .zip will create a coherence directory with several sub-directories.
Installing Coherence
If you are adding Coherence to an application server, you will need to make sure that tangosol.jar and coherence.jar libraries (found in coherence/lib/) are in the CLASSPATH (or the equivalent mechanism that your application server uses).
Alternatively, if your application server supports it, you can package the tangosol.jar and coherence.jar libraries into your application's .ear, .jar or .war file.
For purposes of compilation, you will need to make sure that tangosol.jar and coherence.jar libraries are in the CLASSPATH (or the equivalent mechanism that your compiler or IDE uses).
Verifying that multiple nodes and servers are able to form a cluster
Coherence includes a self-contained console application that can be used to verify that installation is successful and that all the servers that are meant to participate in the cluster are indeed capable of joining the cluster. We recommend that you perform this quick test when you first start using Coherence in a particular network and server environment, to verify that the nodes do indeed connect as expected. You can do that by repeating the following set of steps to start Coherence on each server (you can start multiple instances of Coherence on the same server as well):
- Change the current directory to the Coherence library directory (%COHERENCE_HOME%\lib on Windows and $COHERENCE_HOME/lib on Unix).
- Make sure that the paths are configured so that the Java command will run.
- Run the following command to start Coherence command line:
You should see something like this after you start the first member:
c:\coherence\lib>java -jar coherence.jar
2007-05-23 10:48:16.546 Oracle Coherence 3.3/387 (thread=main, member=n/a): Loaded operational configuration from
resource "jar:file:/C:/coherence/lib/coherence.jar!/tangosol-coherence.xml"
2007-05-23 10:48:16.562 Oracle Coherence 3.3/387 (thread=main, member=n/a): Loaded operational overrides from res
ource "jar:file:/C:/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml"
2007-05-23 10:48:16.562 Oracle Coherence 3.3/387 (thread=main, member=n/a): Optional configuration override "/tango
sol-coherence-override.xml" is not specified
Oracle Coherence Version 3.3/387
Grid Edition: Development mode
Copyright (c) 2000-2007 Oracle. All rights reserved.
2007-05-23 10:48:17.203 Oracle Coherence GE 3.3/387 (thread=Cluster, member=n/a): Service Cluster joined the cluste
r with senior service member n/a
2007-05-23 10:48:20.453 Oracle Coherence GE 3.3/387 (thread=Cluster, member=n/a): Created a new cluster with Memb
er(Id=1, Timestamp=2007-05-23 10:48:17.0, Address=192.168.0.204:8088, MachineId=26828, Location=process:3292@localhost,
Edition=Grid Edition, Mode=Development, CpuCount=2, SocketCount=1) UID=0xC0A800CC00000112B968DF6868CC1F98
SafeCluster: Name=n/a
Group{Address=224.3.3.0, Port=33387, TTL=4}
MasterMemberSet
(
ThisMember=Member(Id=1, Timestamp=2007-05-23 10:48:17.0, Address=192.168.0.204:8088, MachineId=26828)
OldestMember=Member(Id=1, Timestamp=2007-05-23 10:48:17.0, Address=192.168.0.204:8088, MachineId=26828)
ActualMemberSet=MemberSet(Size=1, BitSetCount=2
Member(Id=1, Timestamp=2007-05-23 10:48:17.0, Address=192.168.0.204:8088, MachineId=26828)
)
RecycleMillis=120000
RecycleSet=MemberSet(Size=0, BitSetCount=0
)
)
Services
(
TcpRing{TcpSocketAccepter{State=STATE_OPEN, ServerSocket=192.168.0.204:8088}, Connections=[]}
ClusterService{Name=Cluster, State=(SERVICE_STARTED, STATE_JOINED), Id=0, Version=3.3, OldestMemberId=1}
)
Map (?):
2007-05-23 10:48:53.406 Oracle Coherence GE 3.3/387 (thread=Cluster, member=1): Member(Id=2, Timestamp=2007-05-23 1
0:48:53.218, Address=192.168.0.204:8089, MachineId=26828, Location=process:3356@localhost) joined Cluster with senior member 1
Map (?):
As you can see there is only one member listed in the ActualMemberSet. When the second member is started, you should see something similar to the following at its start up:
c:\coherence\lib>java -jar coherence.jar
2007-05-23 10:48:52.562 Oracle Coherence 3.3/387 (thread=main, member=n/a): Loaded operational configuration from
resource "jar:file:/C:/coherence/lib/coherence.jar!/tangosol-coherence.xml"
2007-05-23 10:48:52.562 Oracle Coherence 3.3/387 (thread=main, member=n/a): Loaded operational overrides from
resource "jar:file:/C:/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml"
2007-05-23 10:48:52.578 Oracle Coherence 3.3/387 (thread=main, member=n/a): Optional configuration override
"/tangosol-coherence-override.xml" is not specified
Oracle Coherence Version 3.3/387
Grid Edition: Development mode
Copyright (c) 2000-2007 Oracle. All rights reserved.
2007-05-23 10:48:53.203 Oracle Coherence GE 3.3/387 (thread=Cluster, member=n/a): Service Cluster joined the cluste
r with senior service member n/a
2007-05-23 10:48:53.421 Oracle Coherence GE 3.3/387 (thread=Cluster, member=n/a): This Member(Id=2, Timestamp=200
7-05-23 10:48:53.218, Address=192.168.0.204:8089, MachineId=26828, Location=process:3356@localhost, Edition=Grid Edition,
Mode=Development, CpuCount=2, SocketCount=1) joined cluster with senior Member(Id=1, Timestamp=2007-05-23 10:48:17.0,
Address=192.168.0.204:8088, MachineId=26828, Location=process:3292@localhost, Edition=Grid Edition, Mode=Development,
CpuCount=2, SocketCount=1)
SafeCluster: Name=n/a
Group{Address=224.3.3.0, Port=33387, TTL=4}
MasterMemberSet
(
ThisMember=Member(Id=2, Timestamp=2007-05-23 10:48:53.218, Address=192.168.0.204:8089, MachineId=26828)
OldestMember=Member(Id=1, Timestamp=2007-05-23 10:48:17.0, Address=192.168.0.204:8088, MachineId=26828)
ActualMemberSet=MemberSet(Size=2, BitSetCount=2
Member(Id=1, Timestamp=2007-05-23 10:48:17.0, Address=192.168.0.204:8088, MachineId=26828)
Member(Id=2, Timestamp=2007-05-23 10:48:53.218, Address=192.168.0.204:8089, MachineId=26828)
)
RecycleMillis=120000
RecycleSet=MemberSet(Size=0, BitSetCount=0
)
)
Services
(
TcpRing{TcpSocketAccepter{State=STATE_OPEN, ServerSocket=192.168.0.204:8089}, Connections=[]}
ClusterService{Name=Cluster, State=(SERVICE_STARTED, STATE_JOINED), Id=0, Version=3.3, OldestMemberId=1}
)
Map (?):
2007-05-23 10:48:54.453 Oracle Coherence GE 3.3/387 (thread=TcpRingListener, member=2): TcpRing: connecting to
member 1 using TcpSocket{State=STATE_OPEN, Socket=Socket[addr=/192.168.0.204,port=1884,localport=8089]}
Map (?):
If you execute the who command at the Map(?): prompt of the first member after the second member is started, you should see the same two members:
Map (?): who
SafeCluster: Name=n/a
Group{Address=224.3.3.0, Port=33387, TTL=4}
MasterMemberSet
(
ThisMember=Member(Id=1, Timestamp=2007-05-23 10:48:17.0, Address=192.168.0.204:8088, MachineId=26828)
OldestMember=Member(Id=1, Timestamp=2007-05-23 10:48:17.0, Address=192.168.0.204:8088, MachineId=26828)
ActualMemberSet=MemberSet(Size=2, BitSetCount=2
Member(Id=1, Timestamp=2007-05-23 10:48:17.0, Address=192.168.0.204:8088, MachineId=26828)
Member(Id=2, Timestamp=2007-05-23 10:48:53.218, Address=192.168.0.204:8089, MachineId=26828)
)
RecycleMillis=120000
RecycleSet=MemberSet(Size=0, BitSetCount=0
)
)
Services
(
TcpRing{TcpSocketAccepter{State=STATE_OPEN, ServerSocket=192.168.0.204:8088}, Connections=[2]}
ClusterService{Name=Cluster, State=(SERVICE_STARTED, STATE_JOINED), Id=0, Version=3.3, OldestMemberId=1}
)
Map (?):
As more new members are started, you should see their addition reflected in the ActualMemberSet list. If you do not see new members being added, your network may not be properly configured for multicast traffic; you may use the Multicast Test to verify this.
Installing Coherence*Web Session Management Module
 | Applies to Coherence 3.0 or later
Please note that the following installation documentation applies to the Coherence*Web module in Coherence release 3.0 or later. This Session Management Module is very different and much more comprehensive than the module provided with Coherence releases prior to Release 2.3. Since we chose to not include the previous version's module jars and documentation in release 2.3 to avoid confusion, if you are looking for information on how to install pre-Release 2.3 module, please refer to the documentation and the User Guide included with the doc directory of the software distribution for the release level you are installing. |
Coherence*Web Session Management Module: Supported Web Containers
The following table summarizes the web containers that are currently supported by the Coherence*Web Session Management Module and the installation information specific to each supported web container. For detailed installation instructions for a particular web container, click on its name.
Notes:
* The server type alias passed to the Coherence*Web installer via the -server command line option.
General Instructions for Installing Coherence*Web Session Management Module
To enable Coherence*Web in your J2EE application, you need to run a ready to deploy application (recommended) through the automated installer prior to deploying it. The automated installer prepares the application for deployment.
To install Coherence*Web for the J2EE application you are deploying:
- Make sure that the application directory, .ear file or .war file are not being used or accessed by another process.
- Change the current directory to the Coherence library directory (%COHERENCE_HOME%\lib on Windows and $COHERENCE_HOME/lib on Unix).
- Make sure that the paths are configured so that the Java command will run.
- Go through the application inspection step by running the following command and specifying the full path to your application and the name of your server found in the chart above (replacing the <app-path> and <server-type> with them in the command line below):
A successful result of this step is the creation (or an update, if it already exists) of the coherence-web.xml configuration descriptor file for your J2EE application in the directory where the application is located. This configuration descriptor contains the default Coherence*Web settings for your application that the installer suggests be used in the following install step. You may at this point proceed to the install step, or review and modify the settings to fit them to your requirements prior to running the install step (which would make the install step use your modified settings). For example, you can enable certain features by setting the "context-param" options in the coherence-web.xml configuration descriptor:
- Go through the Coherence*Web application installation step by running the following command and specifying the full path to your application (replacing the <app-path> with it in the command line below):
Please note that the installer expects to find the valid coherence-web.xml configuration descriptor for its use in the same directory the application is located.
- Deploy the updated application and verify that everything functions as expected, using the load balancer if necessary. Please remember that the load balancer is only intended for testing and should not be used in a production environment.
Installing Coherence*Web Session Management Module on BEATM
WebLogicTM
8.x
The following are additional steps to take when installing the Coherence*Web Session Management Module into a BEA WebLogic 8.x server:
Installing Coherence*Web Session Management Module on BEATM
WebLogicTM
9.x
The following are additional steps to take when installing the Coherence*Web Session Management Module into a BEA WebLogic 9.x server:
Installing Coherence*Web Session Management Module on BEATM
WebLogicTM
Portal 8.1.6+
The following are additional steps to take when installing the Coherence*Web Session Management Module into a BEA WebLogic Portal 8.1.6+ server:
Installing Coherence*Web Session Management Module on Caucho Resin®
3.0.x
The following are additional steps to take when installing the Coherence*Web Session Management Module into a Caucho Resin server:
Installing Coherence*Web Session Management Module on Oracle®
OC4J 10.1.2.x
The following are additional steps to take when installing the Coherence*Web Session Management Module into a Oracle OC4J 10.1.2.x server:
How the Coherence*Web Installer instruments a J2EE application
During the inspect step, the Coherence*Web Installer performs the following tasks:
- Generate a template coherence-web.xml configuration file that contains basic information about the application and target web container along with a set of default Coherence*Web configuration context parameters appropriate for the target web container. If an existing coherence-web.xml configuration file exists (for example, from a previous run of the Coherence*Web Installer), the context parameters in the existing file are merged with those in the generated template.
- Enumerate the JSPs from each web application in the target J2EE application and add information about each JSP to the coherence-web.xml configuration file.
- Enumerate the TLDs from each web application in the target J2EE application and add information about each TLD to the coherence-web.xml configuration file.
During the install step, the Coherence*Web Installer performs the following tasks:
- Create a backup of the original J2EE application so that it can be restored during the uninstall step.
- Add the Coherence*Web configuration context parameters generated in step (1) of the inspect step to the web.xml descriptor of each web application contained in the target J2EE application.
- Unregister any application-specific ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener, HttpSessionListener, and HttpSessionAttributeListener classes (including those registered by TLDs) from each web application.
- Register a Coherence*Web ServletContextListener in each web.xml descriptor. At runtime, the Coherence*Web ServletContextListener will propagate each ServletContextEvent to each application-specific ServletContextListener.
- Register a Coherence*Web ServletContextAttributeListener in each web.xml descriptor. At runtime, the Coherence*Web ServletContextAttributeListener will propagate each ServletContextAttributeEvent to each application-specific ServletContextAttributeListener.
- Wrap each application-specific Servlet declared in each web.xml descriptor with a Coherence*Web SessionServlet. At runtime, each Coherence*Web SessionServlet will delegate to the wrapped Servlet.
- Add the following directive to each JSP enumerated in step (2) of the inspect step: <%@ page extends="com.tangosol.coherence.servlet.api22.JspServlet" %>
During the uninstall step, the Coherence*Web Installer replaces the instrumented J2EE application with the backup of the original version created in step (1) of the install process.
Testing HTTP session management (without a dedicated loadbalancer)
Coherence comes with a light-weight software load balancer; it is only intended for testing purposes. The load balancer is very useful when testing functionality such as Session Management and is very easy to use.
- Start multiple application server processes, on one or more server machines, each running your application on a unique IP address and port combination.
- Open a command (or shell) window.
- Change the current directory to the Coherence library directory (%COHERENCE_HOME%\lib on Windows and $COHERENCE_HOME/lib on Unix).
- Make sure that the paths are configured so that the Java command will run.
- Start the software load balancer with the following command lines (each of these command lines makes the application available on the default HTTP port, which is port 80):
To test load-balancing locally on one machine with two application server instances on ports 7001 and 7002:
To run the load-balancer locally on a machine named server1 that load balances to port 7001 on server1, server2 and server3:
Assuming the above command line, an application that previously was accessed with the URL http://server1:7001/my.jsp would now be accessed with the URL http://server1:80/my.jsp or just http://server1/my.jsp.
The following command line options are supported:
| -backlog |
Sets the TCP/ IP accept backlog option to the specified value, for example:
-backlog=64 |
| -threads |
Uses the specified number of request/ response thread pairs (so the total number of additional daemon threads will be two times the specified value), for example:
-threads=64 |
| -roundrobin |
Specifies the use of a round-robin load-balancing algorithm |
| -random |
Specifies the use of a random load-balancing algorithm (default) |
Make sure that your application uses only relative re-directs or the address or the load-balancer.
Using the Coherence*Web Installer Ant Task
Description
The Coherence*Web Installer Ant task allows you to run the Coherence*Web Installer from within your existing Ant build files. To use the Coherence*Web Installer Ant task, add the following task import statement to your Ant build file:
<taskdef name="cwi" classname="com.tangosol.coherence.misc.CoherenceWebAntTask">
<classpath>
<pathelement location="${coherence.home}/lib/webInstaller.jar"/>
</classpath>
</taskdef>
where ${coherence.home} refers to the root directory of your Coherence installation.
The basic process of installing Coherence*Web into a J2EE application from an Ant build is as follows:
- Build your J2EE application as you normally would
- Run the Coherence*Web Ant task with the operations attribute set to inspect
- Make any necessary changes to the generated Coherence*Web XML descriptor
- Run the Coherence*Web Ant task with the operations attribute set to install
If you are performing iterative development on your application (modifying JSPs, Servlets, static resources, etc.), the installation process would consist of the following steps:
- Run the Coherence*Web Ant task with the operations attribute set to uninstall, the failonerror attribute set to false, and the descriptor attribute set to the location of the previously generated Coherence*Web XML descriptor (from step 2 above)
- Build your J2EE application as you normally would
- Run the Coherence*Web Ant task with the operations attribute set to inspect, install and the descriptor attribute set to the location of the previously generated Coherence*Web XML descriptor (from step 2 above)
If you want to change the Coherence*Web configuration settings of a J2EE application that already has Coherence*Web installed:
- Run the Coherence*Web Ant task with the operations attribute set to uninstall and the descriptor attribute set to the location of the Coherence*Web XML descriptor for the J2EE application.
- Change the necessary configuration parameters in the Coherence*Web XML descriptor.
- Run the Coherence*Web Ant task with the operations attribute set to install and the descriptor attribute set to the location of the modified Coherence*Web XML descriptor (from step 2).
Parameters
| Attribute |
Description |
Required |
| app |
Path to the target J2EE application. This can be a path to a WAR file, an EAR file, an exploded WAR directory, or an exploded EAR directory. |
true, if the operations attribute is set to any value other than version |
| backup |
Path to a directory that will hold a backup of the original target J2EE application. This attribute defaults to the directory that contains the J2EE application. |
false |
| descriptor |
Path to the Coherence*Web XML descriptor. This attribute defaults to coherence-web.xml in the directory that contains the target J2EE application. |
false |
| failonerror |
Stop the Ant build if the Coherence*Web installer exits with a status other than 0. The default is true. |
false |
| nowarn |
Suppress warning messages. This attribute can be either true or false. The default is false. |
false |
| operations |
comma- or space-separated list of operations to perform; each operation must be one of inspect, install, uninstall, or version. |
true |
| server |
The alias of the target J2EE application server. |
false |
| touch |
Touch JSPs and TLDs that are modified by the Coherence*Web installer. This attribute can be either true, false, or 'M/d/y h:mm a'. The default is false. |
false |
| verbose |
Show verbose output. This attribute can be either true or false. The default is false. |
false |
Examples
Inspect the myWebApp.war web application and generate a Coherence*Web XML descriptor called my-coherence-web.xml in the current working directory:
<cwi app="myWebApp.war" operations="inspect" descriptor="my-coherence-web.xml"/>
Install Coherence*Web into the myWebApp.war web application using the Coherence*Web XML descriptor called my-coherence-web.xml found in the current working directory:
<cwi app="myWebApp.war" operations="install" descriptor="my-coherence-web.xml"/>
Uninstall Coherence*Web from the myWebApp.war web application:
<cwi app="myWebApp.war" operations="uninstall">
Install Coherence*Web into the myWebApp.war web application located in the /dev/myWebApp/build directory using the Coherence*Web XML descriptor called my-coherence-web.xml found in the /dev/myWebApp/src directory, and place a backup of the original web application in the /dev/myWebApp/work directory:
<cwi app="/dev/myWebApp/build/myWebApp.war" operations="install" descriptor="/dev/myWebApp/src/my-coherence-web.xml" backup="/dev/myWebApp/work"/>
Install Coherence*Web into the myWebApp.war web application located in the /dev/myWebApp/build directory using the Coherence*Web XML descriptor called coherence-web.xml found in the /dev/myWebApp/build directory. If the web application has not already been inspected (i.e. /dev/myWebApp/build/coherence-web.xml does not exists), inspect the web application prior to installing Coherence*Web:
<cwi app="/dev/myWebApp/build/myWebApp.war" operations="inspect,install"/>
Reinstall Coherence*Web into the myWebApp.war web application located in the /dev/myWebApp/build directory using the Coherence*Web XML descriptor called my-coherence-web.xml found in the /dev/myWebApp/src directory:
<cwi app="/dev/myWebApp/build/myWebApp.war" operations="uninstall,install" descriptor="/dev/myWebApp/src/my-coherence-web.xml"/>
Types of Caches in Coherence
Overview
The following is an overview of the types of caches offered by Coherence. More detail is provided in later sections.
Replicated
Data is fully replicated to every member in the cluster. Offers the fastest read performance. Clustered, fault-tolerant cache with linear performance scalability for reads, but poor scalability for writes (as writes must be processed by every member in the cluster). Because data is replicated to all machines, adding servers does not increase aggregate cache capacity.
Optimistic
OptimisticCache is a clustered cache implementation similar to the ReplicatedCache implementation, but without any concurrency control. This implementation has the highest possible throughput. It also allows to use an alternative underlying store for the cached data (for example, a MRU/MFU-based cache). However, if two cluster members are independently pruning or purging the underlying local stores, it is possible that a cluster member may have a different store content than that held by another cluster member.
Distributed (Partitioned)
Clustered, fault-tolerant cache with linear scalability. Data is partitioned among all the machines of the cluster. For fault-tolerance, partitioned caches can be configured to keep each piece of data on one, two or more unique machines within a cluster.
Near
A hybrid cache; fronts a fault-tolerant, scalable partitioned cache with a local cache. Near cache invalidates front cache entries, using configurable invalidation strategy, and provides excellent performance and synchronization. Near cache backed by a partitioned cache offers zero-millisecond local access for repeat data access, while enabling concurrency and ensuring coherency and fail-over, effectively combining the best attributes of replicated and partitioned caches.
Summary of Cache Types
Numerical Terms:
JVMs = number of JVMs
DataSize = total size of cached data (measured without redundancy)
Redundancy = number of copies of data maintained
LocalCache = size of local cache (for near caches)
| |
Replicated Cache |
Optimistic Cache |
Partitioned Cache |
Near Cache backed by partitioned cache |
VersionedNearCache backed by partitioned cache |
LocalCache not clustered |
| Topology |
Replicated |
Replicated |
Partitioned Cache |
Local Caches + Partitioned Cache |
Local Caches + Partitioned Cache |
Local Cache |
| Fault Tolerance |
Extremely High |
Extremely High |
Configurable 4
Zero to Extremely High |
Configurable 4
Zero to Extremely High |
Configurable 4
Zero to Extremely High |
Zero |
| Read Performance |
Instant 5 |
Instant 5 |
Locally cached: instant 5
Remote: network speed 1 |
Locally cached: instant 5
Remote: network speed 1 |
Locally cached: instant 5
Remote: network speed 1 |
Instant 5 |
| Write Performance |
Fast 2 |
Fast 2 |
Extremely fast 3 |
Extremely fast 3 |
Extremely fast 3 |
Instant 5 |
| Memory Usage (Per JVM) |
DataSize |
DataSize |
DataSize/JVMs x Redundancy |
LocalCache + [DataSize / JVMs] |
LocalCache +
[DataSize/JVMs] |
DataSize |
| Memory Usage (Total) |
JVMs x DataSize |
JVMs x DataSize |
Redundancy x DataSize |
[Redundancy x DataSize] +
[JVMs x LocalCache] |
[Redundancy x DataSize] + [JVMs x LocalCache] |
n/a |
| Coherency |
fully coherent |
fully coherent |
fully coherent |
fully coherent 6 |
fully coherent |
n/a |
| Locking |
fully transactional |
none |
fully transactional |
fully transactional |
fully transactional |
fully transactional |
| Typical Uses |
Metadata |
n/a (see Near Cache) |
Read-write caches |
Read-heavy caches w/ access affinity |
n/a (see Near Cache) |
Local data |
Notes:
1 As a rough estimate, with 100mbit ethernet, network reads typically require ~20ms for a 100KB object. With gigabit ethernet, network reads for 1KB objects are typically sub-millisecond.
2 Requires UDP multicast or a few UDP unicast operations, depending on JVM count.
3 Requires a few UDP unicast operations, depending on level of redundancy.
4 Partitioned caches can be configured with as many levels of backup as desired, or zero if desired. Most installations use one backup copy (two copies total).
5 Limited by local CPU/memory performance, with negligible processing required (typically sub-millisecond performance).
6 Listener-based Near caches are coherent; expiry-based near caches are partially coherent for non-transactional reads and coherent for transactional access.
Cache Semantics
Overview
Coherence caches are used to cache value objects. These objects may represent data from any source, either internal (session data, transient data, etc...) or external (database, mainframe, etc...).
Objects placed in the cache must be capable of being serialized. The simplest approach to doing this is to implement java.io.Serializable. For higher performance, Coherence also supports the java.io.Externalizable and (even faster) com.tangosol.io.ExternalizableLite interfaces. The primary difference between Externalizable and ExternalizableLite is the I/O stream used. In most cases, porting from one to the other is a trivial exercise.
Any objects that implement com.tangosol.run.xml.XmlBean will automatically support ExternalizableLite. For more details, see the API JavaDoc for com.tangosol.run.xml.XmlBean.
As a reminder, when serializing an object, Java serialization automatically crawls every object visible (via object references, including collections like Map and List). As a result, cached objects should not refer to their parent objects directly (holding onto an identifying value like an integer is okay). Of course, objects that implement their own serialization routines do not need to worry about this.
Creating and Using Coherence Caches
Overview
The simplest and most flexible way to create caches in Coherence is to use the cache configuration descriptor to define attributes and names for your application's or cluster's caches, and to instantiate the caches in your application code referring to them by name that matches the names or patterns as defined in the descriptor.
This approach to configuring and using Coherence caches has a number of very important benefits. It separates the cache initialization and access logic for the cache in your application from its attributes and characteristics. This way your code is written in a way that is independent of the cache type that will be utilized in your application deployment and changing the characteristics of each cache (such as cache type, cache eviction policy, and cache type-specific attributes, etc.) can be done without making any changes to the code whatsoever. It allows you to create multiple configurations for the same set of named caches and to instruct your application to use the appropriate configuration at deployment time by specifying the descriptor to use in the java command line when the node JVM is started.
Creating a cache in your application.
To instantiate a cache in your application code, you need to:
- Make sure that coherence.jar and tangosol.jar are in your classpath.
- Use CacheFactory.getCache() to access the cache in your code.
Your code will look similar to the following:
import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
...
NamedCache cache = CacheFactory.getCache("VirtualCache");
Now you can retrieve and store objects in the cache, using the NamedCache API, which extends the standard java.util.Map interface, adding a number of additional capabilities that provide concurrency control (ConcurrentMap interface), ability to listen for cache changes (ObservableMap interface) and ability to query the cache (QueryMap interface).
The following is an example of typical cache operations:
String key = "key";
MyValue value = (MyValue) cache.get(key);
cache.put(key, value);
Configuring the caches
The cache attributes and settings are defined in the cache configuration descriptor. Cache attributes determine the cache type (what means and resources the cache will use for storing, distributing and synchronizing the cached data) and cache policies (what happens to the objects in the cache based on cache size, object longevity and other parameters).
The structure of the cache configuration descriptor (described in detail by the cache-config.dtd included in the coherence.jar) consists of two primary sections: caching-schemes section and caching-scheme-mapping section.
The caching-schemes section is where the attributes of a cache or a set of caches get defined. The caching schemes can be of a number of types, each with its own set of attributes. The caching schemes can be defined completely from scratch, or can incorporate attributes of other existing caching schemes, referring to them by their scheme-names (using a scheme-ref element) and optionally overriding some of their attributes to create new caching schemes. This flexibility enables you to create caching scheme structures that are easy to maintain, foster reuse and are very flexible.
The caching-scheme-mapping section is where the specific cache name or a naming pattern is attached to the cache scheme that defines the cache configuration to use for the cache that matches the name or the naming pattern.
So if we would like to define the cache descriptor for the cache we mentioned in the previous section (VirtualCache), it may look something like the following:
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<!--
Caches with any name will be created as default replicated.
-->
<cache-mapping>
<cache-name>*</cache-name>
<scheme-name>default-replicated</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<!--
Default Replicated caching scheme.
-->
<replicated-scheme>
<scheme-name>default-replicated</scheme-name>
<service-name>ReplicatedCache</service-name>
<backing-map-scheme>
<class-scheme>
<scheme-ref>default-backing-map</scheme-ref>
</class-scheme>
</backing-map-scheme>
</replicated-scheme>
<!--
Default backing map scheme definition used by all
The caches that do not require any eviction policies
-->
<class-scheme>
<scheme-name>default-backing-map</scheme-name>
<class-name>com.tangosol.util.SafeHashMap</class-name>
</class-scheme>
</caching-schemes>
</cache-config>
The above cache configuration descriptor specifies that all caches will be created (including our VirtualCache cache) utilizing the default-replicated caching scheme. It defines the default-replicated caching scheme as a replicated-scheme, utilizing a service named ReplicatedCache and utilizing the backing map named default-backing-map, which is defined as a class com.tangosol.util.SafeHashMap (the default backing map storage that Coherence uses when no eviction policies are required).
Then, at a later point, let's say we decide that, since the number of entries that our cache is holding is too large and updates to the objects too frequent to use a replicated cache, we want our VirtualCache cache to become a distributed cache instead (while keeping all other caches replicated). To accommodate these new circumstances, we can change the cache configuration by adding the following cache-scheme definition for the distributed cache to the caching-schemes section:
<!--
Default Distributed caching scheme.
-->
<distributed-scheme>
<scheme-name>default-distributed</scheme-name>
<service-name>DistributedCache</service-name>
<backing-map-scheme>
<class-scheme>
<scheme-ref>default-backing-map</scheme-ref>
</class-scheme>
</backing-map-scheme>
</distributed-scheme>
and then mapping the VirtualCache cache to it in the caching-schemes-mapping section:
<cache-mapping>
<cache-name>VirtualCache</cache-name>
<scheme-name>default-distributed</scheme-name>
</cache-mapping>
The resulting cache definition descriptor will look as follows:
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<!--
Caches with any name will be created as default replicated.
-->
<cache-mapping>
<cache-name>*</cache-name>
<scheme-name>default-replicated</scheme-name>
</cache-mapping>
<cache-mapping>
<cache-name>VirtualCache</cache-name>
<scheme-name>default-distributed</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<!--
Default Replicated caching scheme.
-->
<replicated-scheme>
<scheme-name>default-replicated</scheme-name>
<service-name>ReplicatedCache</service-name>
<backing-map-scheme>
<class-scheme>
<scheme-ref>default-backing-map</scheme-ref>
</class-scheme>
</backing-map-scheme>
</replicated-scheme>
<!--
Default Distributed caching scheme.
-->
<distributed-scheme>
<scheme-name>default-distributed</scheme-name>
<service-name>DistributedCache</service-name>
<backing-map-scheme>
<class-scheme>
<scheme-ref>default-backing-map</scheme-ref>
</class-scheme>
</backing-map-scheme>
</distributed-scheme>
<!--
Default backing map scheme definition used by all
The caches that do not require any eviction policies
-->
<class-scheme>
<scheme-name>default-backing-map</scheme-name>
<class-name>com.tangosol.util.SafeHashMap</class-name>
</class-scheme>
</caching-schemes>
</cache-config>
Once we revise and deploy the descriptor and restart the cluster, the VirtualCache cache will be a distributed cache instead of replicated, all without any changes to the code we wrote.
Cache Configuration Descriptor location
A few words about how to instruct Coherence where to find the cache configuration descriptor. Without specifying anything in the command java command line, Coherence will attempt to use the cache configuration descriptor named coherence-cache-config.xml that it finds in the classpath. Since Coherence ships with this file packaged into the coherence.jar, unless you place another file with the same name in the classpath location preceding coherence.jar, that is the one that Coherence will use. You can tell Coherence to use a different default descriptor by using the -Dtangosol.coherence.cacheconfig java command line property as follows:
The above command instructs Coherence to use my-config.xml file in /cfg directory as the default cache configuration descriptor. As you can see, this capability can give you the flexibility to modify the cache configurations of your applications without making any changes to the application code and by simply specifying different cache configuration descriptors at application deployment or start-up.
Putting it all together: your first Coherence cache example
Let's try walking through creating a working example cache using the caches and the cache configuration descriptor we described in the previous section. The easiest way to initially do that is to use the Coherence command line application. A couple of general comments regarding this example before we get started:
- In the examples we refer to the 'nodes' or 'JVMs'. We make no assumption as to where they will run - you can run all of them on the same machine multiple machines or a combination of multiple nodes per machine and multiple machines. To see the clustered cache in action you will need at least 2 nodes to see the JVMs sharing data (all the following examples were captured with 2 JVMs on a single machine).
- This example uses Windows conventions and commands but it will work equally well in any of the Unix environments (with the appropriate adjustments for the Unix commands and conventions) and we encourage you to try it on multiple machines with different operating systems, as this is the way Coherence is designed to function: on multiple platforms simultaneously.
Setting up your test environment
To set up the test environment, you will need install Coherence by unzipping the software distribution in the desired location on one or more machines.
The coherence/examples directory of the software contains the following examples that we will be making use of in this exercise:
- examples/config/explore-config.xml is the configuration descriptor we will use.
- examples/java/com/tangosol/examples/explore/SimpleCacheExplorer.java is the java class that demonstrates how you can access the cache from a command line.
To deploy and run it, you need to execute the following java command line (from the tangosol directory):
- In Windows:
- In Unix:
You should see something like the following when you bring it up:
T:\tangosol>java -cp ./lib/coherence.jar;./lib/tangosol.jar;./examples/java
-Dtangosol.coherence.cacheconfig=./examples/config/explore-config.xml
com.tangosol.examples.explore.SimpleCacheExplorer
2007-05-23 11:53:40.203 Oracle Coherence 3.3/387 (thread=main, member=n/a): Loaded operational configuration from
resource "jar:file:/C:/coherence/lib/coherence.jar!/tangosol-coherence.xml"
2007-05-23 11:53:40.203 Oracle Coherence 3.3/387 (thread=main, member=n/a): Loaded operational overrides from
resource "jar:file:/C:/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml"
2007-05-23 11:53:40.203 Oracle Coherence 3.3/387 (thread=main, member=n/a): Optional configuration override
"/tangosol-coherence-override.xml" is not specified
Oracle Coherence Version 3.3/387
Grid Edition: Development mode
Copyright (c) 2000-2007 Oracle. All rights reserved.
2007-05-23 11:53:40.390 Oracle Coherence GE 3.3/387 (thread=main, member=n/a): Loaded cache configuration from
file "C:\coherence\examples\config\explore-config.xml"
2007-05-23 11:53:40.890 Oracle Coherence GE 3.3/387 (thread=Cluster, member=n/a): Service Cluster joined the
cluster with senior service member n/a
2007-05-23 11:53:44.140 Oracle Coherence GE 3.3/387 (thread=Cluster, member=n/a): Created a new cluster with
Member(Id=1, Timestamp=2007-05-23 11:53:40.687, Address=192.168.0.204:8088, MachineId=26828, Location=process:3976@localhost,
Edition=Grid Edition, Mode=Development, CpuCount=2, SocketCount=1) UID=0xC0A800CC00000112B9A4BE4F68CC1F98
2007-05-23 11:53:44.203 Oracle Coherence GE 3.3/387 (thread=ReplicatedCache, member=1): Service ReplicatedCache
joined the cluster with senior service member 1
Command: help
clear
get
keys
info
put
quit
remove
Command:
Typing in 'info' will show you the configuration and the other member information (please note that in the following example there are 2 cluster members active):
Command: info
>> VirtualCache cache is using a cache-scheme named 'default-replicated' defined as:
default-replicated
ReplicatedCache
default-backing-map
>> The following member nodes are currently active:
Member(Id=1, Timestamp=2007-05-23 11:53:40.687, Address=192.168.0.204:8088, MachineId=26828, Location=process:3976@localhost
) <-- this node
Member(Id=2, Timestamp=2007-05-23 11:56:04.125, Address=192.168.0.204:8089, MachineId=26828, Location=process:2216@localhost
)
You can also put a value into the cache:
Command: put 1 One
>> Put Complete
Command:
And retrieve a value from the cache:
Command: get 1
>> Value is One
Command:
Try these commands from multiple sessions and see the results.
The examples/jsp/explore/SimpleCacheExplorer.jsp is the JSP file that can be used with your favorite application server:
- To deploy and run it, you will need to deploy the JSP to the default web applications directory of your application server (along with the contents of the examples/jsp/images directory), modify the server start-up script to make sure that the classpath includes tangosol.jar and coherence.jar, and specify the location of the cache configuration file on the Java command line using the -Dtangosol.coherence.cacheconfig option (e.g. -Dtangosol.coherence.cacheconfig=$COHERENCE_HOME/examples/config/explore-config.xml).
- You can then start one or more instances of the application server (on different machines or different ports) and access the SimpleCacheExplorer.jsp from the browser. You should see something like the following when you bring it up:
As with the command line application please try adding, updating and removing entries from multiple instances of the application server. Also please notice the information about the cache configuration and cluster membership at the bottom of the page. As cluster members are added and removed, this information will change.
Modifying the cache configuration
Once you are comfortable with the test setup, let's change the cache configuration and test our changes, using this simple test harness. Please remember that after each cache configuration change all the cluster members need to be shut down and then restarted (whether you are using application server instances or just plain java JVMs). All our test are configured to use coherence/examples/config/explore-config.xml, so this the file that needs to be edited to make cache configuration changes.
Let's make the first change we described previously, changing the VirtualCache to be a distributed cache by adding the following (bolded) sections:
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<!--
Caches with any name will be created as default replicated.
-->
<cache-mapping>
<cache-name>*</cache-name>
<scheme-name>default-replicated</scheme-name>
</cache-mapping>
<cache-mapping>
<cache-name>VirtualCache</cache-name>
<scheme-name>default-distributed</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<!--
Default Replicated caching scheme.
-->
<replicated-scheme>
<scheme-name>default-replicated</scheme-name>
<service-name>ReplicatedCache</service-name>
<backing-map-scheme>
<class-scheme>
<scheme-ref>default-backing-map</scheme-ref>
</class-scheme>
</backing-map-scheme>
</replicated-scheme>
<!--
Default Distributed caching scheme.
-->
<distributed-scheme>
<scheme-name>default-distributed</scheme-name>
<service-name>DistributedCache</service-name>
<backing-map-scheme>
<class-scheme>
<scheme-ref>default-backing-map</scheme-ref>
</class-scheme>
</backing-map-scheme>
</distributed-scheme>
<!--
Default backing map scheme definition used by all
The caches that do not require any eviction policies
-->
<class-scheme>
<scheme-name>default-backing-map</scheme-name>
<class-name>com.tangosol.util.SafeHashMap</class-name>
</class-scheme>
</caching-schemes>
</cache-config>
After the changes are saved, the test intances are restarted and you have had a chance to do some test data entry to see how the cache behaves, you should see the following in the cache configuration section of the tests:
- SimpleCacheExplorer.java:
Command: info
>> VirtualCache cache is using a cache-scheme named 'default-distributed' defined as:
default-distributed
DistributedCache
default-backing-map
>> The following member nodes are currently active:
Member(Id=1, Timestamp=Mon Jun 27 09:49:37 EDT 2005, Address=192.168.0.247, Port=8088, MachineId=26871)
Member(Id=2, Timestamp=Mon Jun 27 09:49:43 EDT 2005, Address=192.168.0.247, Port=8089, MachineId=26871) <-- this node
Command:
- SimpleCacheExplorer.jsp:
As you can see, our VirtualCache cache is now distributed according to the cache configuration descriptor.
Now let's add an eviction policy for our default distributed cache, limiting it's size to 5 entries (per node) and setting the entry expiry to 60 seconds with an LRU eviction policy. To do that we need to make the following (bolded) changes to our descriptor:
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<!--
Caches with any name will be created as default replicated.
-->
<cache-mapping>
<cache-name>*</cache-name>
<scheme-name>default-replicated</scheme-name>
</cache-mapping>
<cache-mapping>
<cache-name>VirtualCache</cache-name>
<scheme-name>default-distributed</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<!--
Default Replicated caching scheme.
-->
<replicated-scheme>
<scheme-name>default-replicated</scheme-name>
<service-name>ReplicatedCache</service-name>
<backing-map-scheme>
<class-scheme>
<scheme-ref>default-backing-map</scheme-ref>
</class-scheme>
</backing-map-scheme>
</replicated-scheme>
<!--
Default Distributed caching scheme.
-->
<distributed-scheme>
<scheme-name>default-distributed</scheme-name>
<service-name>DistributedCache</service-name>
<backing-map-scheme>
<local-scheme>
<scheme-ref>default-eviction</scheme-ref>
<eviction-policy>LRU</eviction-policy>
<high-units>5</high-units>
<expiry-delay>60</expiry-delay>
</local-scheme>
</backing-map-scheme>
</distributed-scheme>
<!--
Default backing map scheme definition used by all
The caches that do not require any eviction policies
-->
<class-scheme>
<scheme-name>default-backing-map</scheme-name>
<class-name>com.tangosol.util.SafeHashMap</class-name>
</class-scheme>
<!--
Default eviction policy scheme.
-->
<local-scheme>
<scheme-name>default-eviction</scheme-name>
<eviction-policy>HYBRID</eviction-policy>
<high-units>0</high-units>
<expiry-delay>3600</expiry-delay>
</local-scheme>
</caching-schemes>
</cache-config>
Please note that we defined a general purpose local-scheme 'default-eviction' (with no size limit, 5 minute expiry and a HYBRID eviction policy) and then used it by reference (using scheme-ref) for our default-distributed scheme definition, overriding it's configuration settings to match our requirements.
After the changes are saved, the test intances are restarted and you have had a chance to do some test data entry to see how the cache behaves, you should see the following in the cache configuration section of the tests:
- SimpleCacheExplorer.java:
Command: info
>> VirtualCache cache is using a cache-scheme named 'default-distributed' defined as:
default-distributed
DistributedCache
default-eviction
LRU
5
60
>> The following member nodes are currently active:
Member(Id=1, Timestamp=Mon Jun 27 09:50:07 EDT 2005, Address=192.168.0.247, Port=8088, MachineId=26871)
Member(Id=2, Timestamp=Mon Jun 27 09:50:17 EDT 2005, Address=192.168.0.247, Port=8089, MachineId=26871) <-- this node
Command:
Try doing some puts and gets, carefully noting the time you last updated the specific entries. You should see that the number of entries does not exceed 5 entries per node (so if you have 2 nodes running the number of entries should not exceed 10, for 3 nodes - 15, and so on) and entries either expire after they have not been updated for 60 seconds, or when you add the 6th entry (with the least recently touched entries being 'evicted' from the cache first. (Hint: use the 'keys' command in the SimpleCacheExplorer.java to see the list of keys in the cache.)
These examples show you the general approach to modifying the cache configurations without making any code changes (as you no doubt noticed we did not touch our test application's code). Please refer to the cache-config.dtd, which can be found in the coherence.jar for full details on the available cache configuration descriptor settings and the explanation of their meaning and possible settings.
Configuring and Using Coherence*Extend
Overview
Coherence*ExtendTM
extends the reach of the core Coherence TCMP cluster to a wider range of consumers, including desktops, remote servers and machines located across WAN connections. Typical uses of Coherence*Extend include providing desktop applications with access to Coherence caches (including support for Near Cache and Continuous Query) and Coherence cluster "bridges" that link together multiple Coherence clusters connected via a high-latency, unreliable WAN.
Coherence*Extend consists of two basic components: a client and a Coherence*Extend clustered service hosted by one or more DefaultCacheServer processes. The adapter library includes implementations of both the CacheService and InvocationService interfaces that route all requests to a Coherence*Extend clustered service instance running within the Coherence cluster. The Coherence*Extend clustered service in turn responds to client requests by delegating to an actual Coherence clustered service (for example, a Partitioned or Replicated cache service). The client adapter library and Coherence*Extend clustered service use a low-level messaging protocol to communicate with each other. Coherence*Extend includes the following transport bindings for this protocol:
- Extend-JMSTM
: uses your existing JMS infrastructure as the means to connect to the cluster
- Extend-TCPTM
: uses a high performance, scalable TCP/IP-based communication layer to connect to the cluster
The choice of a transport binding is configuration-driven and is completely transparent to the client application that uses Coherence*Extend. A Coherence*Extend service is retrieved just like a Coherence clustered service: via the CacheFactory class. Once it is obtained, a client uses the Coherence*Extend service in the same way as it would if it were part of the Coherence cluster. The fact that operations are being sent to a remote cluster node (over either JMS or TCP) is transparent to the client application.
General Instructions
Configuring and using Coherence*Extend requires four basic steps:
- Create a client-side Coherence cache configuration descriptor that includes one or more <remote-cache-scheme> and/or <remote-invocation-scheme> configuration elements
- Create a cluster-side Coherence cache configuration descriptor that includes one or more <proxy-scheme> configuration elements
- Launch one or more DefaultCacheServer processes
- Create a client application that uses one or more Coherence*Extend services
- Launch the client application
The following sections describe each of these steps in detail for the Extend-JMS and Extend-TCP transport bindings.
Configuring and Using Coherence*Extend-JMS
Client-side Cache Configuration Descriptor
A Coherence*Extend client that uses the Extend-JMS transport binding must define a Coherence cache configuration descriptor which includes a <remote-cache-scheme> and/or <remote-invocation-scheme> element with a child <jms-initiator> element containing various JMS-specific configuration information. For example:
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<cache-mapping>
<cache-name>dist-extend</cache-name>
<scheme-name>extend-dist</scheme-name>
</cache-mapping>
<cache-mapping>
<cache-name>dist-extend-near</cache-name>
<scheme-name>extend-near</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<near-scheme>
<scheme-name>extend-near</scheme-name>
<front-scheme>
<local-scheme>
<high-units>1000</high-units>
</local-scheme>
</front-scheme>
<back-scheme>
<remote-cache-scheme>
<scheme-ref>extend-dist</scheme-ref>
</remote-cache-scheme>
</back-scheme>
<invalidation-strategy>all</invalidation-strategy>
</near-scheme>
<remote-cache-scheme>
<scheme-name>extend-dist</scheme-name>
<service-name>ExtendJmsCacheService</service-name>
<initiator-config>
<jms-initiator>
<queue-connection-factory-name>jms/coherence/ConnectionFactory</queue-connection-factory-name>
<queue-name>jms/coherence/Queue</queue-name>
<connect-timeout>10s</connect-timeout>
</jms-initiator>
<outgoing-message-handler>
<request-timeout>5s</request-timeout>
</outgoing-message-handler>
</initiator-config>
</remote-cache-scheme>
<remote-invocation-scheme>
<scheme-name>extend-invocation</scheme-name>
<service-name>ExtendJmsInvocationService</service-name>
<initiator-config>
<jms-initiator>
<queue-connection-factory-name>jms/coherence/ConnectionFactory</queue-connection-factory-name>
<queue-name>jms/coherence/Queue</queue-name>
<connect-timeout>10s</connect-timeout>
</jms-initiator>
<outgoing-message-handler>
<request-timeout>5s</request-timeout>
</outgoing-message-handler>
</initiator-config>
</remote-invocation-scheme>
</caching-schemes>
</cache-config>
This cache configuration descriptor defines two caching schemes, one that uses Extend-JMS to connect to a remote Coherence cluster (<remote-cache-scheme>) and one that maintains an in-process size-limited near cache of remote Coherence caches (again, accessed via Extend-JMS). Additionally, the cache configuration descriptor defines a <remote-invocation-scheme> that allows the client application to execute tasks within the remote Coherence cluster. Both the <remote-cache-scheme> and <remote-invocation-scheme> elements have a <jms-initiator> child element which includes all JMS-specific information needed to connect the client with the Coherence*Extend clustered service running within the remote Coherence cluster.
When the client application retrieves a NamedCache via the CacheFactory using, for example, the name "dist-extend", the Coherence*Extend adapter library will connect to the Coherence cluster via a JMS Queue (retrieved via JNDI using the name "jms/coherence/Queue") and return a NamedCache implementation that routes requests to the NamedCache with the same name running within the remote cluster. Likewise, when the client application retrieves a InvocationService by calling CacheFactory.getConfigurableCacheFactory().ensureService("ExtendJmsInvocationService"), the Coherence*Extend adapter library will connect to the Coherence cluster via the same JMS Queue and return an InvocationService implementation that executes synchronous Invocable tasks within the remote clustered JVM to which the client is connected.
Cluster-side Cache Configuration Descriptor
In order for a Coherence*Extend-JMSTM
client to connect to a Coherence cluster, one or more DefaultCacheServer processes must be running that use a Coherence cache configuration descriptor which includes a <proxy-scheme> element with a child <jms-acceptor> element containing various JMS-specific configuration information. For example:
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<cache-mapping>
<cache-name>dist-*</cache-name>
<scheme-name>dist-default</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<distributed-scheme>
<scheme-name>dist-default</scheme-name>
<lease-granularity>member</lease-granularity>
<backing-map-scheme>
<local-scheme/>
</backing-map-scheme>
<autostart>true</autostart>
</distributed-scheme>
<proxy-scheme>
<service-name>ExtendJmsProxyService</service-name>
<acceptor-config>
<jms-acceptor>
<queue-connection-factory-name>jms/coherence/ConnectionFactory</queue-connection-factory-name>
<queue-name>jms/coherence/Queue</queue-name>
</jms-acceptor>
</acceptor-config>
<autostart>true</autostart>
</proxy-scheme>
</caching-schemes>
</cache-config>
This cache configuration descriptor defines two clustered services, one that uses Extend-JMS to allow remote Extend-JMS clients to connect to the Coherence cluster and a standard Partitioned cache service. Since this descriptor is used by a DefaultCacheServer it is important that the <autostart> configuration element for each service is set to true so that clustered services are automatically restarted upon termination. The <proxy-scheme> element has a <jms-acceptor> child element which includes all JMS-specific information needed to accept client connection requests over JMS.
The Coherence*Extend clustered service will listen to a JMS Queue (retrieved via JNDI using the name "jms/coherence/Queue") for connection requests. When, for example, a client attempts to connect to a Coherence NamedCache called "dist-extend", the Coherence*Extend clustered service will proxy subsequent requests to the NamedCache with the same name which, in this case, will be a Parititioned cache. Note that Extend-JMS client connection requests will be load balanced across all DefaultCacheServer processes that are running a Coherence*Extend clustered service with the same configuration.
Configuring your JMS Provider
Coherence*Extend-JMS uses JNDI to obtain references to all JMS resources. To specify the JNDI properties that Coherence*Extend-JMS uses to create a JNDI InitialContext, create a file called jndi.properties that contains your JMS provider's configuration properties and add the directory that contains the file to both the client application and DefaultCacheServer classpaths.
For example, if you are using WebLogic Server as your JMS provider, your jndi.properties file would look something like the following:
Additionally, Coherence*Extend-JMS uses a JMS Queue to connect Extend-JMS clients to a Coherence*Extend clustered service instance. Therefore, you must deploy an appropriately configured JMS QueueConnectionFactory and Queue and register them under the JNDI names specified in the <jms-initiator> and <jms-acceptor> configuration elements.
For example, if you are using WebLogic Server, you can use the following Ant script to create and deploy these JMS resources:
ant create.domain -->
domain/startmydomain.cmd|sh -->
<project name="extend-jms-wls" default="create.domain" basedir=".">
<property name="weblogic.home" value="c:/opt/bea/weblogic8.1.5"/>
<property name="weblogic.jar" value="${weblogic.home}/server/lib/weblogic.jar"/>
<property name="server.user" value="system"/>
<property name="server.password" value="weblogic"/>
<property name="domain.dir" value="domain"/>
<property name="domain.name" value="mydomain"/>
<property name="server.name" value="myserver"/>
<property name="realm.name" value="myrealm"/>
<property name="server.host" value="localhost"/>
<property name="server.port" value="7001"/>
<property name="admin.url" value="t3://${server.host}:${server.port}"/>
<path id="project.classpath">
<pathelement location="${weblogic.jar}"/>
</path>
<taskdef name="wlserver"
classname="weblogic.ant.taskdefs.management.WLServer"
classpathref="project.classpath"/>
<taskdef name="wlconfig"
classname="weblogic.ant.taskdefs.management.WLConfig"
classpathref="project.classpath"/>
<target name="clean" description="Remove all build artifacts.">
<delete dir="${domain.dir}"/>
</target>
<target name="create.domain"
description="Create a WLS domain for use with Coherence*Extend-JMS.">
<delete dir="${domain.dir}"/>
<mkdir dir="${domain.dir}"/>
<wlserver weblogicHome="${weblogic.home}"
dir="${domain.dir}"
classpathref="project.classpath"
host="${server.host}"
port="${server.port}"
servername="${server.name}"
domainname="${domain.name}"
generateConfig="true"
username="${server.user}"
password="${server.password}"
action="start"/>
<antcall target="config.domain"/>
</target>
<target name="config.domain"
description="Configure a WLS domain for use with Coherence*Extend-JMS.">
<wlconfig url="${admin.url}"
username="${server.user}"
password="${server.password}">
<query domain="${domain.name}"
type="Server"
name="${server.name}"
property="server"/>
<create type="JMSTemplate" name="CoherenceTemplate" property="template"/>
<create type="JMSServer" name="MyJMSServer">
<set attribute="Targets" value="${server}"/>
<create type="JMSQueue" name="CoherenceQueue">
<set attribute="JNDIName" value="jms/coherence/Queue"/>
</create>
<set attribute="TemporaryTemplate" value="${template}"/>
</create>
<create type="JMSConnectionFactory" name="CoherenceConnectionFactory">
<set attribute="JNDIName"
value="jms/coherence/ConnectionFactory"/>
<set attribute="Targets" value="${server}"/>
</create>
</wlconfig>
</target>
</project>
Launching an Extend-JMS DefaultCacheServer Process
To start a DefaultCacheServer that uses the cluster-side Coherence cache configuration described earlier to allow Extend-JMS clients to connect to the Coherence cluster via JMS, you need to do the following:
- Change the current directory to the Coherence library directory (%COHERENCE_HOME%\lib on Windows and $COHERENCE_HOME/lib on Unix)
- Make sure that the paths are configured so that the Java command will run
- Start the DefaultCacheServer command line application with the directory that contains your jndi.properties file and your JMS provider's libraries on the classpath and the -Dtangosol.coherence.cacheconfig system property set to the location of the cluster-side Coherence cache configuration descriptor described earlier
For example, if you are using WebLogic server as your JMS provider, you would run the following command on Windows (note that it is broken up into multiple lines here only for formatting purposes; this is a single command typed on one line):
On Unix:
Launching an Extend-JMS Client Application
To start a client application that uses Extend-JMS to connect to a remote Coherence cluster via JMS, you need to do the following:
- Change the current directory to the Coherence library directory (%COHERENCE_HOME%\lib on Windows and $COHERENCE_HOME/lib on Unix)
- Make sure that the paths are configured so that the Java command will run
- Start your client application with the directory that contains your jndi.properties file and your JMS provider's libraries on the classpath and the -Dtangosol.coherence.cacheconfig system property set to the location of the client-side Coherence cache configuration descriptor described earlier
For example, if you are using WebLogic server as your JMS provider, you would run the following command on Windows (note that it is broken up into multiple lines here only for formatting purposes; this is a single command typed on one line):
On Unix:
Configuring and Using Coherence*Extend-TCP
Client-side Cache Configuration Descriptor
A Coherence*Extend client that uses the Extend-TCP transport binding must define a Coherence cache configuration descriptor which includes a <remote-cache-scheme> and/or <remote-invocation-scheme> element with a child <tcp-initiator> element containing various TCP/IP-specific configuration information. For example:
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<cache-mapping>
<cache-name>dist-extend</cache-name>
<scheme-name>extend-dist</scheme-name>
</cache-mapping>
<cache-mapping>
<cache-name>dist-extend-near</cache-name>
<scheme-name>extend-near</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<near-scheme>
<scheme-name>extend-near</scheme-name>
<front-scheme>
<local-scheme>
<high-units>1000</high-units>
</local-scheme>
</front-scheme>
<back-scheme>
<remote-cache-scheme>
<scheme-ref>extend-dist</scheme-ref>
</remote-cache-scheme>
</back-scheme>
<invalidation-strategy>all</invalidation-strategy>
</near-scheme>
<remote-cache-scheme>
<scheme-name>extend-dist</scheme-name>
<service-name>ExtendTcpCacheService</service-name>
<initiator-config>
<tcp-initiator>
<remote-addresses>
<socket-address>
<address>localhost</address>
<port>9099</port>
</socket-address>
</remote-addresses>
<connect-timeout>10s</connect-timeout>
</tcp-initiator>
<outgoing-message-handler>
<request-timeout>5s</request-timeout>
</outgoing-message-handler>
</initiator-config>
</remote-cache-scheme>
<remote-invocation-scheme>
<scheme-name>extend-invocation</scheme-name>
<service-name>ExtendTcpInvocationService</service-name>
<initiator-config>
<tcp-initiator>
<remote-addresses>
<socket-address>
<address>localhost</address>
<port>9099</port>
</socket-address>
</remote-addresses>
<connect-timeout>10s</connect-timeout>
</tcp-initiator>
<outgoing-message-handler>
<request-timeout>5s</request-timeout>
</outgoing-message-handler>
</initiator-config>
</remote-invocation-scheme>
</caching-schemes>
</cache-config>
This cache configuration descriptor defines two caching schemes, one that uses Extend-TCP to connect to a remote Coherence cluster (<remote-cache-scheme>) and one that maintains an in-process size-limited near cache of remote Coherence caches (again, accessed via Extend-TCP). Additionally, the cache configuration descriptor defines a <remote-invocation-scheme> that allows the client application to execute tasks within the remote Coherence cluster. Both the <remote-cache-scheme> and <remote-invocation-scheme> elements have a <tcp-initiator> child element which includes all TCP/IP-specific information needed to connect the client with the Coherence*Extend clustered service running within the remote Coherence cluster.
When the client application retrieves a NamedCache via the CacheFactory using, for example, the name "dist-extend", the Coherence*Extend adapter library will connect to the Coherence cluster via TCP/IP (using the address "localhost" and port 9099) and return a NamedCache implementation that routes requests to the NamedCache with the same name running within the remote cluster. Likewise, when the client application retrieves a InvocationService by calling CacheFactory.getConfigurableCacheFactory().ensureService("ExtendTcpInvocationService"), the Coherence*Extend adapter library will connect to the Coherence cluster via TCP/IP (again, using the address "localhost" and port 9099) and return an InvocationService implementation that executes synchronous Invocable tasks within the remote clustered JVM to which the client is connected.
Note that the <remote-addresses> configuration element can contain multiple <socket-address> child elements. The Coherence*Extend adapter library will attempt to connect to the addresses in a random order, until either the list is exhausted or a TCP/IP connection is established.
Cluster-side Cache (a.k.a Coherence Extend Proxy) Configuration Descriptor
In order for a Coherence*Extend-TCPTM
client to connect to a Coherence cluster, one or more DefaultCacheServer processes must be running that use a Coherence cache configuration descriptor which includes a <proxy-scheme> element with a child <tcp-acceptor> element containing various TCP/IP-specific configuration information. For example:
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<caching-scheme-mapping>
<cache-mapping>
<cache-name>dist-*</cache-name>
<scheme-name>dist-default</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<distributed-scheme>
<scheme-name>dist-default</scheme-name>
<lease-granularity>member</lease-granularity>
<backing-map-scheme>
<local-scheme/>
</backing-map-scheme>
<autostart>true</autostart>
</distributed-scheme>
<proxy-scheme>
<service-name>ExtendTcpProxyService</service-name>
<thread-count>5</thread-count>
<acceptor-config>
<tcp-acceptor>
<local-address>
<address>localhost</address>
<port>9099</port>
</local-address>
</tcp-acceptor>
</acceptor-config>
<autostart>true</autostart>
</proxy-scheme>
</caching-schemes>
</cache-config>
This cache configuration descriptor defines two clustered services, one that uses Extend-TCP to allow remote Extend-TCP clients to connect to the Coherence cluster and a standard Partitioned cache service. Since this descriptor is used by a DefaultCacheServer it is important that the <autostart> configuration element for each service is set to true so that clustered services are automatically restarted upon termination. The <proxy-scheme> element has a <tcp-acceptor> child element which includes all TCP/IP-specific information needed to accept client connection requests over TCP/IP.
The Coherence*Extend clustered service will listen to a TCP/IP ServerSocket (bound to address "localhost" and port 9099) for connection requests. When, for example, a client attempts to connect to a Coherence NamedCache called "dist-extend-direct", the Coherence*Extend clustered service will proxy subsequent requests to the NamedCache with the same name which, in this case, will be a Partitioned cache.
Launching an Extend-TCP DefaultCacheServer Process
To start a DefaultCacheServer that uses the cluster-side Coherence cache configuration described earlier to allow Extend-TCP clients to connect to the Coherence cluster via TCP/IP, you need to do the following:
- Change the current directory to the Coherence library directory (%COHERENCE_HOME%\lib on Windows and $COHERENCE_HOME/lib on Unix)
- Make sure that the paths are configured so that the Java command will run
- Start the DefaultCacheServer command line application with the -Dtangosol.coherence.cacheconfig system property set to the location of the cluster-side Coherence cache configuration descriptor described earlier
For example (note that the following command is broken up into multiple lines here only for formatting purposes; this is a single command typed on one line):
Launching an Extend-TCP Client Application
To start a client application that uses Extend-TCP to connect to a remote Coherence cluster via TCP/IP, you need to do the following:
- Change the current directory to the Coherence library directory (%COHERENCE_HOME%\lib on Windows and $COHERENCE_HOME/lib on Unix)
- Make sure that the paths are configured so that the Java command will run
- Start your client application with the -Dtangosol.coherence.cacheconfig system property set to the location of the client-side Coherence cache configuration descriptor described earlier
For example (note that the following command is broken up into multiple lines here only for formatting purposes; this is a single command typed on one line):
Example Coherence*Extend Client Application
The following example demonstrates how to retrieve and use a Coherence*Extend CacheService and InvocationService. This example increments an Integer value in a remote Partitioned cache and then retrieves the value by executing an Invocable on the clustered JVM to which the client is attached:
public static void main(String[] asArg)
throws Throwable
{
NamedCache cache = CacheFactory.getCache("dist-extend");
Integer IValue = (Integer) cache.get("key");
if (IValue == null)
{
IValue = new Integer(1);
}
else
{
IValue = new Integer(IValue.intValue() + 1);
}
cache.put("key", IValue);
InvocationService service = (InvocationService)
CacheFactory.getConfigurableCacheFactory()
.ensureService("ExtendTcpInvocationService");
Map map = service.query(new AbstractInvocable()
{
public void run()
{
setResult(CacheFactory.getCache("dist-extend").get("key"));
}
}, null);
Integer IValue = (Integer) map.get(service.getCluster().getLocalMember());
}
Note that this example could also be run on a Coherence node (i.e. within the cluster) verbatum. The fact that operations are being sent to a remote cluster node (over either JMS or TCP) is completely transparent to the client application.
 | Coherence*Extend InvocationService
Since, by definition, a Coherence*Extend client has no direct knowledge of the cluster and the members running within the cluster, the Coherence*Extend InvocationService only allows Invocable tasks to be executed on the JVM to which the client is connected. Therefore, you should always pass a null member set to the query() method. As a consequence of this, the single result of the execution will be keyed by the local Member, which will be null if the client is not part of the cluster. This Member can be retrieved by calling service.getCluster().getLocalMember(). Additionally, the Coherence*Extend InvocationService only supports synchronous task execution (i.e. the execute() method is not supported). |
Advanced Configuration
Network Filters
Like Coherence clustered services, Coherence*Extend services support pluggable network filters. Filters can be used to modify the contents of network traffic before it is placed "on the wire". Most standard Coherence network filters are supported, including the compression and symmetric encryption filters. For more information on configuring filters, see the Network Filters section.
To use network filters with Coherence*Extend, a <use-filters> element must be added to the <initiator-config> element in the client-side cache configuration descriptor and to the <acceptor-config> element in the cluster-side cache configuration descriptor.
For example, to encrypt network traffic exchanged between a Coherence*Extend client and the clustered service to which it is connected, configure the client-side <remote-cache-scheme> and <remote-invocation-scheme> elements like so (assuming the symmetric encryption filter has been named symmetric-encryption):
<remote-cache-scheme>
<scheme-name>extend-dist</scheme-name>
<service-name>ExtendTcpCacheService</service-name>
<initiator-config>
<tcp-initiator>
<remote-addresses>
<socket-address>
<address>localhost</address>
<port>9099</port>
</socket-address>
</remote-addresses>
<connect-timeout>10s</connect-timeout>
</tcp-initiator>
<outgoing-message-handler>
<request-timeout>5s</request-timeout>
</outgoing-message-handler>
<use-filters>
<filter-name>symmetric-encryption</filter-name>
</use-filters>
</initiator-config>
</remote-cache-scheme>
<remote-invocation-scheme>
<scheme-name>extend-invocation</scheme-name>
<service-name>ExtendTcpInvocationService</service-name>
<initiator-config>
<tcp-initiator>
<remote-addresses>
<socket-address>
<address>localhost</address>
<port>9099</port>
</socket-address>
</remote-addresses>
<connect-timeout>10s</connect-timeout>
</tcp-initiator>
<outgoing-message-handler>
<request-timeout>5s</request-timeout>
</outgoing-message-handler>
<use-filters>
<filter-name>symmetric-encryption</filter-name>
</use-filters>
</initiator-config>
</remote-invocation-scheme>
and the cluster-side <proxy-scheme> element like so:
<proxy-scheme>
<service-name>ExtendTcpProxyService</service-name>
<thread-count>5</thread-count>
<acceptor-config>
<tcp-acceptor>
<local-address>
<address>localhost</address>
<port>9099</port>
</local-address>
</tcp-acceptor>
<use-filters>
<filter-name>symmetric-encryption</filter-name>
</use-filters>
</acceptor-config>
<autostart>true</autostart>
</proxy-scheme>
 |
The contents of the <use-filters> element must be the same in the client and cluster-side cache configuration descriptors. |
Connection Error Detection and Failover
When a Coherence*Extend service detects that the connection between the client and cluster has been severed (for example, due to a network, software, or hardware failure), the Coherence*Extend client service implementation (i.e. CacheService or InvocationService) will dispatch a MemberEvent.MEMBER_LEFT event to all registered MemberListeners and the service will be stopped. If the client application attempts to subsequently use the service, the service will automatically restart itself and attempt to reconnect to the cluster. If the connection is successful, the service will dispatch a MemberEvent.MEMBER_JOINED event; otherwise, a fatal exception will be thrown to the client application.
A Coherence*Extend service has several mechanisms for detecting dropped connections. Some mechanisms are inherent to the underlying protocol (i.e. a javax.jms.ExceptionListener in Extend-JMS and TCP/IP in Extend-TCP), whereas others are implemented by the service itself. The latter mechanisms are configured via the <outgoing-message-handler> configuration element.
The primary configurable mechanism used by a Coherence*Extend client service to detect dropped connections is a request timeout. When the service sends a request to the remote cluster and does not receive a response within the request timeout interval (see <request-timeout>), the service assumes that the connection has been dropped. The Coherence*Extend client and clustered services can also be configured to send a periodic heartbeat over the connection (see <heartbeat-interval> and <heartbeat-timeout>). If the service does not receive a response within the configured heartbeat timeout interval, the service assumes that the connection has been dropped.
 |
You should always enable heartbeats when using a connectionless transport, as is the case with Extend-JMS. |
 |
If you do not specify a <request-timeout/>, a Coherence*Extend service will use an infinite request timeout. In general, this is not a recommended configuration, as it could result in an unresponsive application. For most use cases, you should specify a reasonable finite request timeout. |
Read-only NamedCache Access
By default, the Coherence*Extend clustered service allows both read and write access to proxied NamedCache instances. If you would like to prohibit Coherence*Extend clients from modifying cached content, you may do so using the <cache-service-proxy> child configuration element. For example:
<proxy-scheme>
...
<proxy-config>
<cache-service-proxy>
<read-only>true</read-only>
</cache-service-proxy>
</proxy-config>
<autostart>true</autostart>
</proxy-scheme>
Client-side NamedCache Locking
By default, the Coherence*Extend clustered service disallows Coherence*Extend clients from acquiring NamedCache locks. If you would like to enable client-side locking, you may do so using the <cache-service-proxy> child configuration element. For example:
<proxy-scheme>
...
<proxy-config>
<cache-service-proxy>
<lock-enabled>true</lock-enabled>
</cache-service-proxy>
</proxy-config>
<autostart>true</autostart>
</proxy-scheme>
If you do enable client-side locking and your client application makes use of the NamedCache.lock() and unlock() methods, it is important that you specified the member-based rather than thread-based locking strategy for any Partitioned or Replicated cache services defined in your cluster-side Coherence cache configuration descriptor. The reason being is that the Coherence*Extend clustered service uses a pool of threads to execute client requests concurrently; therefore, it cannot be guaranteed that the same thread will execute subsequent requests from the same Coherence*Extend client.
To specify the member-based locking strategy for a Partitioned or Replicated cache service, use the <lease-granularity> configuration element. For example:
<distributed-scheme>
<scheme-name>dist-default</scheme-name>
<lease-granularity>member</lease-granularity>
<backing-map-scheme>
<local-scheme/>
</backing-map-scheme>
<autostart>true</autostart>
</distributed-scheme>
Code Examples and FAQ
Unable to render {include} Couldn't find a page to include called: Code Examples and FAQ
Technical Overview
Clustering
Overview
Coherence is built on a fully clustered architecture. Since "clustered" is an overused term in the industry, it is worth stating exactly what it means to say that Coherence is clustered. Coherence is based on a peer-to-peer clustering protocol, using a conference room model, in which servers are capable of:
- Speaking to Everyone: When a party enters the conference room, it is able to speak to all other parties in a conference room.
- Listening: Each party present in the conference room can hear messages that are intended for everyone, as well as messages that are intended for that particular party. It is also possible that a message is not heard the first time, thus a message may need to be repeated until it is heard by its intended recipients.
- Discovery: Parties can only communicate by speaking and listening; there are no other senses. Using only these means, the parties must determine exactly who is in the conference room at any given time, and parties must detect when new parties enter the conference room.
- Working Groups and Private Conversations: Although a party can talk to everyone, once a party is introduced to the other parties in the conference room (i.e. once discovery has completed), the party can communicate directly to any set of parties, or directly to an individual party.
- Death Detection: Parties in the conference room must quickly detect when parties leave the conference room – or die.
Using the conference room model provides the following benefits:
- There is no configuration required to add members to a cluster. Subject to configurable security restrictions, any JVM running Coherence will automatically join the cluster and be able to access the caches and other services provided by the cluster. This includes J2EE application servers, Cache Servers, dedicated cache loader processes, or any other JVM that is running with the Coherence software. When a JVM joins the cluster, it is called a cluster node, or alternatively, a cluster member.
- Since all cluster members are known, it is possible to provide redundancy within the cluster, such that the death of any one JVM or server machine does not cause any data to be lost.
- Since the death or departure of a cluster member is automatically and quickly detected, failover occurs very rapidly, and more importantly, it occurs transparently, which means that the application does not have to do any extra work to handle failover.
- Since all cluster members are known, it is possible to load balance responsibilities across the cluster. Coherence does this automatically with its Distributed Cache Service, for example. Load balancing automatically occurs to respond to new members joining the cluster, or existing members leaving the cluster.
- Communication can be very well optimized, since some communication is multi-point in nature (e.g. messages for everyone), and some is between two members.
Two of the terms used here describe processing for failed servers:
- Failover: Failover refers to the ability of a server to assume the responsibilities of a failed server. For example, "When the server died, its processes failed over to the backup server."
- Failback: Failback is an extension to failover that allows a server to reclaim its responsibilities once it restarts. For example, "When the server came back up, the processes that it was running previously were failed back to it."
All of the Coherence clustered services, including cache services and grid services, provide automatic and transparent failover and failback. While these features are transparent to the application, it should be noted that the application can sign up for events to be notified of all comings and goings in the cluster.
Cache Topologies
Overview
Coherence supports many different cache topologies, but generally they fall into a few general categories. Because Coherence uses the TCMP clustering protocol, Coherence can supports each of these without compromise. However, there are inherent advantages of each topology, and trade-offs between them.
Topology as a concept refers to where the data physically resides and how it is accessed in a distributed environment. It is important to understand that, regardless of where the data physically resides, and which topology is being used, every cluster participant has the same logical view of the data, and uses the same exact API to access the data. Generally, this means that the topology can be tuned or even selected at deployment time.
- Peer-to-Peer: For most purposes, a peer-to-peer topology is the easiest to configure and offers very good performance. A peer-to-peer topology is one in which each server both provides and consumes the same clustered services. For example, in a cluster of J2EE application servers, if those servers are all managing and consuming data, and the data are shared via replicated and/or distributed caches, that would be a peer-to-peer topology. This topology spreads the load evenly over as many servers as possible, minimizing configuration details and offering the cache services the greatest overall amounts of memory and CPU resources.
- Centralized (Cache Servers): In order to centralize cache management to a cluster of servers, yet provide access to other servers, a centralized cache topology is used. This has several benefits, including the ability to reduce the resource requirements of the servers that utilize the cache by completely offloading cache management from those servers. Additionally, the cache servers (those that actually manage the cache data) can be hosted on machines explicitly configured for that purpose, and those machines do not require a J2EE application server or any other software other than a standard JVM. The cluster of cache servers (so designated by their storage-enabled attribute) provides a unified cache image, such that this model could almost be described as a cache client/cache server architecture, with the exception being that the server part of it is composed of a cluster of any number of actual servers. The obvious benefits of the clustered cache server architecture is the transparent failover and failback, and the ability to provision new servers to expand the caching resources, both in terms of processing power and in-memory cache sizes. This topology uses the Coherence Distributed Cache Service, which is a cluster-partitioned cache.
- Multi-Tier (n-tier): While the Centralized topology is a two-tier architecture, it is possible to extend this topology to three or more tiers, by having each tier be a client of the tier behind it, and coupling these tiers either over a clustered protocol (such as TCMP) or via JMS in cases where real-time cache coherency is not required and communication protocols are limited (such as production server tiers in which only certain protocols are permitted.)
- Hybrid (Near Caching): To accelerate cache accesses for Centralized and Multi-Tier topologies, Coherence supports a hybrid topology using a Near Cache technology. A Near Cache provides local cache access to recently- and/or often-used data, backed by a centralized or multi-tiered cache that is used to load-on-demand for local cache misses. Near Caches have configurable levels of cache coherency, from the most basic expiry-based caches and invalidation-based caches, up to advanced data-versioning caches that can provide guaranteed coherency. The result is a tunable balance between the preservation of local memory resources and the performance benefits of truly local caches.
The extent of the cluster and of its tiers is fully definable in the tangosol-coherence.xml configuration file. This includes the ability to lock down the set of servers that can access and manage the cache for security purposes. The selection of which topology to use is typically driven by the cache configuration file, which by default is named coherence-cache-config.xml; however, the topology can also be driven entirely by the Coherence programmatic API, if the developer so chooses.
Cluster Services Overview
Overview
Coherence functionality is based on the concept of cluster services. Each cluster node can participate in (which implies both the ability to provide and to consume) any number of named services. These named services may already exist, which is to say that they may already be running on one or more other cluster nodes, or a cluster node can register new named services. Each named service has a service name that uniquely identifies the service within the cluster, and a service type, which defines what the service can do. There are several service types that are supported by Coherence:
- Cluster Service: This service is automatically started when a cluster node needs to join the cluster; each cluster node always has exactly one service of this type running. This service is responsible for the detection of other cluster nodes, for detecting the failure (death) of a cluster node, and for registering the availability of other services in the cluster. In other words, the Cluster Service keeps track of the membership and services in the cluster.
- Distributed Cache Service: This is the distributed cache service, which allows cluster nodes to distribute (partition) data across the cluster so that each piece of data in the cache is managed (held) by only one cluster node. The Distributed Cache Service supports pessimistic locking. Additionally, to support failover without any data loss, the service can be configured so that each piece of data will be backed up by one or more other cluster nodes. Lastly, some cluster nodes can be configured to hold no data at all; this is useful, for example, to limit the Java heap size of an application server process, by setting the application server processes to not hold any distributed data, and by running additional cache server JVMs to provide the distributed cache storage.
- Invocation Service: This service provides clustered invocation and supports grid computing architectures. Using the Invocation Service, application code can invoke agents on any node in the cluster, or any group of nodes, or across the entire cluster. The agent invocations can be request/response, fire and forget, or an asynchronous user-definable model.
- Optimistic Cache Service: This is the optimistic-concurrency version of the Replicated Cache Service, which fully replicates all of its data to all cluster nodes, and employs an optimization similar to optimistic database locking in order to maintain coherency. Coherency refers to the fact that all servers will end up with the same "current" value, even if multiple updates occur at the same exact time from different servers. The Optimistic Cache Service does not support pessimistic locking, so in general it should only be used for caching "most recently known" values for read-only uses.
- Replicated Cache Service: This is the synchronized replicated cache service, which fully replicates all of its data to all cluster nodes that are running the service. Furthermore, it supports pessimistic locking so that data can be modified in a cluster without encountering the classic missing update problem.
Regarding resources, a clustered service typically uses one daemon thread, and optionally has a thread pool that can be configured to provide the service with additional processing bandwidth. For example, the invocation service and the distributed cache service both fully support thread pooling in order to accelerate database load operations, parallel distributed queries, and agent invocations.
It is important to note that these are only the basic clustered services, and not the full set of types of caches provided by Coherence. By combining clustered services with cache features such as backing maps and overflow maps, Coherence can provide an extremely flexible, configurable and powerful set of options for clustered applications. For example, the Near Cache functionality uses a Distributed Cache as one of its components.
Within a cache service, there exists any number of named caches. A named cache provides the standard JCache API, which is based on the Java collections API for key-value pairs, known as java.util.Map. The Map interface is the same API that is implemented by the Java Hashtable class, for example.
Replicated Cache Service
Overview
The first type of cache that Coherence supported was the replicated cache, and it was an instant success due to its ability to handle data replication, concurrency control and failover in a cluster, all while delivering in-memory data access speeds. A clustered replicated cache is exactly what it says it is: a cache that replicates its data to all cluster nodes.
There are several challenges to building a reliable replicated cache. The first is how to get it to scale and perform well. Updates to the cache have to be sent to all cluster nodes, and all cluster nodes have to end up with the same data, even if multiple updates to the same piece of data occur at the same time. Also, if a cluster node requests a lock, it should not have to get all cluster nodes to agree on the lock, otherwise it will scale extremely poorly; yet in the case of cluster node failure, all of the data and lock information must be kept safely. Coherence handles all of these scenarios transparently, and provides the most scalable and highly available replicated cache implementation available for Java applications.
The best part of a replicated cache is its access speed. Since the data is replicated to each cluster node, it is available for use without any waiting. This is referred to as "zero latency access," and is perfect for situations in which an application requires the highest possible speed in its data access. Each cluster node (JVM) accesses the data from its own memory:

In contrast, updating a replicated cache requires pushing the new version of the data to all other cluster nodes:

Coherence implements its replicated cache service in such a way that all read-only operations occur locally, all concurrency control operations involve at most one other cluster node, and only update operations require communicating with all other cluster nodes. The result is excellent scalable performance, and as with all of the Coherence services, the replicated cache service provides transparent and complete failover and failback.
The limitations of the replicated cache service should also be carefully considered. First, however much data is managed by the replicated cache service is on each and every cluster node that has joined the service. That means that memory utilization (the Java heap size) is increased for each cluster node, which can impact performance. Secondly, replicated caches with a high incidence of updates will not scale linearly as the cluster grows; in other words, the cluster will suffer diminishing returns as cluster nodes are added.
Partitioned Cache Service
Overview
To address the potential scalability limits of the replicated cache service, both in terms of memory and communication bottlenecks, Coherence has provided a distributed cache service since release 1.2. Many products have used the term distributed cache to describe their functionality, so it is worth clarifying exactly what is meant by that term in Coherence. Coherence defines a distributed cache as a collection of data that is distributed (or, partitioned) across any number of cluster nodes such that exactly one node in the cluster is responsible for each piece of data in the cache, and the responsibility is distributed (or, load-balanced) among the cluster nodes.
There are several key points to consider about a distributed cache:
- Partitioned: The data in a distributed cache is spread out over all the servers in such a way that no two servers are responsible for the same piece of cached data. This means that the size of the cache and the processing power associated with the management of the cache can grow linearly with the size of the cluster. Also, it means that operations against data in the cache can be accomplished with a "single hop," in other words, involving at most one other server.
- Load-Balanced: Since the data is spread out evenly over the servers, the responsibility for managing the data is automatically load-balanced across the cluster.
- Location Transparency: Although the data is spread out across cluster nodes, the exact same API is used to access the data, and the same behavior is provided by each of the API methods. This is called location transparency, which means that the developer does not have to code based on the topology of the cache, since the API and its behavior will be the same with a local JCache, a replicated cache, or a distributed cache.
- Failover: All Coherence services provide failover and failback without any data loss, and that includes the distributed cache service. The distributed cache service allows the number of backups to be configured; as long as the number of backups is one or higher, any cluster node can fail without the loss of data.
Access to the distributed cache will often need to go over the network to another cluster node. All other things equals, if there are n cluster nodes, (n - 1) / n operations will go over the network:

Since each piece of data is managed by only one cluster node, an access over the network is only a "single hop" operation. This type of access is extremely scalable, since it can utilize point-to-point communication and thus take optimal advantage of a switched network.
Similarly, a cache update operation can utilize the same single-hop point-to-point approach, which addresses one of the two known limitations of a replicated cache, the need to push cache updates to all cluster nodes:

In figure 4, above, the data is being sent to a primary cluster node and a backup cluster node. This is for failover purposes, and corresponds to a backup count of one. (The default backup count setting is one.) If the cache data were not critical, which is to say that it could be re-loaded from disk, the backup count could be set to zero, which would allow some portion of the distributed cache data to be lost in the event of a cluster node failure. If the cache were extremely critical, a higher backup count, such as two, could be used. The backup count only affects the performance of cache modifications, such as those made by adding, changing or removing cache entries.
Modifications to the cache are not considered complete until all backups have acknowledged receipt of the modification. This means that there is a slight performance penalty for cache modifications when using the distributed cache backups; however it guarantees that if a cluster node were to unexpectedly fail, that data consistency is maintained and no data will be lost.
Failover of a distributed cache involves promoting backup data to be primary storage. When a cluster node fails, all remaining cluster nodes determine what data each holds in backup that the failed cluster node had primary responsible for when it died. Those data becomes the responsibility of whatever cluster node was the backup for the data:

If there are multiple levels of backup, the first backup becomes responsible for the data; the second backup becomes the new first backup, and so on. Just as with the replicated cache service, lock information is also retained in the case of server failure, with the sole exception being that the locks for the failed cluster node are automatically released.
The distributed cache service also allows certain cluster nodes to be configured to store data, and others to be configured to not store data. The name of this setting is local storage enabled. Cluster nodes that are configured with the local storage enabled option will provide the cache storage and the backup storage for the distributed cache. Regardless of this setting, all cluster nodes will have the same exact view of the data, due to location transparency.

There are several benefits to the local storage enabled option:
- The Java heap size of the cluster nodes that have turned off local storage enabled will not be affected at all by the amount of data in the cache, because that data will be cached on other cluster nodes. This is particularly useful for application server processes running on older JVM versions with large Java heaps, because those processes often suffer from garbage collection pauses that grow exponentially with the size of the heap.
- Coherence allows each cluster node to run any supported version of the JVM. That means that cluster nodes with local storage enabled turned on could be running a newer JVM version that supports larger heap sizes, or Coherence's off-heap storage using the Java NIO features.
- The local storage enabled option allows some cluster nodes to be used just for storing the cache data; such cluster nodes are called Coherence cache servers. Cache servers are commonly used to scale up Coherence's distributed query functionality.
Local Storage
Overview
The Coherence architecture is modular, allowing almost any piece to be extended or even replaced with a custom implementation. One of the responsibilities of the Coherence system that is completely configurable, extendable and replaceable is local storage. Local storage refers to the data structures that actually store or cache the data that is managed by Coherence. For an object to provide local storage, it must support the same standard collections interface, java.util.Map. When a local storage implementation is used by Coherence to store replicated or distributed data, it is called a backing map, because Coherence is actually backed by that local storage implementation. The other common uses of local storage is in front of a distributed cache and as a backup behind the distributed cache.
Typically, Coherence uses one of the following local storage implementations:
- Safe HashMap: This is the default lossless implementation. A lossless implementation is one, like Java's Hashtable class, that is neither size-limited nor auto-expiring. In other words, it is an implementation that never evicts ("loses") cache items on its own. This particular HashMap implementation is optimized for extremely high thread-level concurrency. (For the default implementation, use class com.tangosol.util.SafeHashMap; when an implementation is required that provides cache events, use com.tangosol.util.ObservableHashMap. These implementations are thread-safe.)
- Local Cache: This is the default size-limiting and/or auto-expiring implementation. The local cache is covered in more detail below, but the primary points to remember about it are that it can limit the size of the cache, and it can automatically expire cache items after a certain period of time. (For the default implementation, use com.tangosol.net.cache.LocalCache; this implementation is thread safe and supports cache events, com.tangosol.net.CacheLoader, CacheStore and configurable/pluggable eviction policies.)
- Read/Write Backing Map: This is the default backing map implementation for caches that load from a database on a cache miss. It can be configured as a read-only cache (consumer model) or as either a write-through or a write-behind cache (for the consumer/producer model). The write-through and write-behind modes are intended only for use with the distributed cache service. If used with a near cache and the near cache must be kept in sync with the distributed cache, it is possible to combine the use of this backing map with a Seppuku-based near cache (for near cache invalidation purposes); however, given these requirements, it is suggested that the versioned implementation be used. (For the default implementation, use class com.tangosol.net.cache.ReadWriteBackingMap.)
- Versioned Backing Map: This is an optimized version of the read/write backing map that optimizes its handling of the data by utilizing a data versioning technique. For example, to invalidate near caches, it simply provides a version change notification, and to determine whether cached data needs to be written back to the database, it can compare the persistent (database) version information with the transient (cached) version information. The versioned implementation can provide very balanced performance in large scale clusters, both for read-intensive and write-intensive data. (For the default implementation, use class com.tangosol.net.cache.VersionedBackingMap; with this backing map, you can optionally use the com.tangosol.net.cache.VersionedNearCache as a near cache implementation.)
- Binary Map (Java NIO): This is a backing map implementation that can store its information in memory but outside of the Java heap, or even in memory-mapped files, which means that it does not affect the Java heap size and the related JVM garbage-collection performance that can be responsible for application pauses. This implementation is also available for distributed cache backups, which is particularly useful for read-mostly and read-only caches that require backup for high availability purposes, because it means that the backup does not affect the Java heap size yet it is immediately available in case of failover.
- Serialization Map: This is a backing map implementation that translates its data to a form that can be stored on disk, referred to as a serialized form. It requires a separate com.tangosol.io.BinaryStore object into which it stores the serialized form of the data; usually, this is the built-in LH disk store implementation, but the Serialization Map supports any custom implementation of BinaryStore. (For the default implementation of Serialization Map, use com.tangosol.net.cache.SerializationMap.)
- Serialization Cache: This is an extension of the SerializationMap that supports an LRU eviction policy. This can be used to limit the size of disk files, for example. (For the default implementation of Serialization Cache, use com.tangosol.net.cache.SerializationCache.)
- Overflow Map: An overflow map doesn't actually provide storage, but it deserves mention in this section because it can tie together two local storage implementations so that when the first one fills up, it will overflow into the second. (For the default implementation of OverflowMap, use com.tangosol.net.cache.OverflowMap.)
Local Cache
Overview
While it is not a clustered service, the Coherence local cache implementation is often used in combination with various Coherence clustered cache services. The Coherence local cache is just that: A cache that is local to (completely contained within) a particular cluster node. There are several attributes of the local cache that are particularly interesting:
- The local cache implements the same standard collections interface that the clustered caches implement, meaning that there is no programming difference between using a local or a clustered cache. Just like the clustered caches, the local cache is tracking to the JCache API, which itself is based on the same standard collections API that the local cache is based on.
- The local cache can be size-limited. This means that the local cache can restrict the number of entries that it caches, and automatically evict entries when the cache becomes full. Furthermore, both the sizing of entries and the eviction policies are customizable, for example allowing the cache to be size-limited based on the memory utilized by the cached entries. The default eviction policy uses a combination of Most Frequently Used (MFU) and Most Recently Used (MRU) information, scaled on a logarithmic curve, to determine what cache items to evict. This algorithm is the best general-purpose eviction algorithm because it works well for short duration and long duration caches, and it balances frequency versus recentness to avoid cache thrashing. The pure LRU and pure LFU algorithms are also supported, as well as the ability to plug in custom eviction policies.
- The local cache supports automatic expiration of cached entries, meaning that each cache entry can be assigned a time to live in the cache. Furthermore, the entire cache can be configured to flush itself on a periodic basis or at a preset time.
- The local cache is thread safe and highly concurrent, allowing many threads to simultaneously access and update entries in the local cache.
- The local cache supports cache notifications. These notifications are provided for additions (entries that are put by the client, or automatically loaded into the cache), modifications (entries that are put by the client, or automatically reloaded), and deletions (entries that are removed by the client, or automatically expired, flushed, or evicted.) These are the same cache events supported by the clustered caches.
- The local cache maintains hit and miss statistics. These runtime statistics can be used to accurately project the effectiveness of the cache, and adjust its size-limiting and auto-expiring settings accordingly while the cache is running.
The local cache is important to the clustered cache services for several reasons, including as part of Coherence's near cache technology, and with the modular backing map architecture.
Best Practices
Overview
Coherence supports several cache topologies, but these options cover the vast majority of use cases. All are fully coherent and support cluster-wide locking and transactions:
- Replicated - Each machine contains a full copy of the dataset. Read access is instantaneous.
- Partitioned (Distributed) - Each machine contains a unique partition of the dataset. Adding machines to the cluster will increase the capacity of the cache. Both read and write access involve network transfer and serialization/deserialization.
- Near - Each machine contains a small local cache which is synchronized with a larger Partitioned cache, optimizing read performance. There is some overhead involved with synchronizing the caches.
Data Access Patterns
Data access distribution (hot spots)
When caching a large dataset, typically a small portion of that dataset will be responsible for most data accesses. For example, in a 1000 object dataset, 80% of operations may be be against a 100 object subset. The remaining 20% of operations may be against the other 900 objects. Obviously the most effective return on investment will be gained by caching the 100 most active objects; caching the remaining 900 objects will provide 25% more effective caching while requiring a 900% increase in resources.
On the other hand, if every object is accessed equally often (for example in sequential scans of the dataset), then caching will require more resources for the same level of effectiveness. In this case, achieving 80% cache effectiveness would require caching 80% of the dataset versus 10%. (Note that sequential scans of partially cached data sets will generally defeat MRU, LFU and MRU-LFU eviction policies). In practice, almost all non-synthetic (benchmark) data access patterns are uneven, and will respond well to caching subsets of data.
In cases where a subset of data is active, and a smaller subset is particularly active, Near caching can be very beneficial when used with the "all" invalidation strategy (this is effectively a two-tier extension of the above rules).
Cluster-node affinity
Coherence's Near cache technology will transparently take advantage of cluster-node affinity, especially when used with the "present" invalidation strategy. This topology is particularly useful when used in conjunction with a sticky load-balancer. Note that the "present" invalidation strategy results in higher overhead (as opposed to "all") when the front portion of the cache is "thrashed" (very short lifespan of cache entries); this is due to the higher overhead of adding/removing key-level event listeners. In general, a cache should be tuned to avoid thrashing and so this is usually not an issue.
Read-write ratio and data sizes
Generally speaking, the following cache topologies are best for the following use cases:
Replicated cache - small amounts of read-heavy data (e.g. metadata)
Partitioned cache - large amounts of read-write data (e.g. large data caches)
Near cache - similar to Partitioned, but has further benefits from read-heavy tiered access patterns (e.g. large data caches with hotspots) and "sticky" data access (e.g. sticky HTTP session data). Depending on the synchronization method (expiry, asynchronous, synchronous), the worst case performance may range from similar to a Partitioned cache to considerably worse.
Interleaving
Interleaving refers to the number of cache reads between each cache write. The Partitioned cache is not affected by interleaving (as it is designed for 1:1 interleaving). The Replicated and Near caches by contrast are optimized for read-heavy caching, and prefer a read-heavy interleave (e.g. 10 reads between every write). This is because they both locally cache data for subsequent read access. Writes to the cache will force these locally cached items to be refreshed, a comparatively expensive process (relative to the near-zero cost of fetching an object off the local memory heap). Note that with the Near cache technology, worst-case performance is still similar to the Partitioned cache; the loss of performance is relative to best-case scenarios.
Note that interleaving is related to read-write ratios, but only indirectly. For example, a Near cache with a 1:1 read-write ratio may be extremely fast (all writes followed by all reads) or much slower (1:1 interleave, write-read-write-read...).
Heap Size Considerations
Using several small heaps
For large datasets, Partitioned or Near caches are recommended. As the scalability of the Partitioned cache is linear for both reading and writing, varying the number of Coherence JVMs will not significantly affect cache performance. On the other hand, JVM memory management routines show worse than linear scalability. For example, increasing JVM heap size from 512MB to 2GB may substantially increase garbage collection (GC) overhead and pauses.
For this reason, it is common to use multiple Coherence instances per physical machine. As a general rule of thumb, current JVM technology works well up to 512MB heap sizes. Thererfore, using a number of 512MB Coherence instances will provide optimal performance without a great deal of JVM configuration or tuning.
For performance-sensitive applications, experimentation may provide better tuning. When considering heap size, it is important to find the right balance. The lower bound is determined by per-JVM overhead (and also, manageability of a potentially large number of JVMs). For example, if there is a fixed overhead of 100MB for infrastructure software (e.g. JMX agents, connection pools, internal JVM structures), then the use of JVMs with 256MB heap sizes will result in close to 40% overhead for non-cache data. The upper bound on JVM heap size is governed by memory management overhead, specifically the maximum duration of GC pauses and the percentage of CPU allocated to GC (and other memory management tasks).
For Java 5 VMs running on commodity systems, the following rules generally hold true (with no JVM configuration tuning). With a heap size of 512MB, GC pauses will not exceed one second. With a heap size of 1GB, GC pauses are limited to roughly 2-3 seconds. With a heap size of 2GB, GC pauses are limited to roughly 5-6 seconds. It is important to note that GC tuning will have an enormous impact on GC throughput and pauses. In all configurations, initial (-Xms) and maximum (-Xmx) heap sizes should be identical. There are many variations that can substantially impact these numbers, including machine architecture, CPU count, CPU speed, JVM configuration, object count (object size), object access profile (short-lived versus long-lived objects).
For allocation-intensive code, GC can theoretically consume close to 100% of CPU usage. For both cache server and client configurations, most CPU resources will typically be consumed by application-specific code. It may be worthwhile to view verbose garbage collection statistics (e.g. -verbosegc). Use the profiling features of the JVM to get profiling information including CPU usage by GC (e.g. -Xprof).
Moving the cache out of the application heap
Using dedicated Coherence cache server instances for Partitioned cache storage will minimize the heap size of application JVMs as the data is no longer stored locally. As most Partitioned cache access is remote (with only 1/N of data being held locally), using dedicated cache servers does not generally impose much additional overhead. Near cache technology may still be used, and it will generally have a minimal impact on heap size (as it is caching an even smaller subset of the Partitioned cache). Many applications are able to dramatically reduce heap sizes, resulting in better responsiveness.
Local partition storage may be enabled (for cache servers) or disabled (for application server clients) with the tangosol.coherence.distributed.localstorage Java property (e.g. -Dtangosol.coherence.distributed.localstorage=false).
It may also be disabled by modifying the <local-storage> setting in the tangosol-coherence.xml (or tangosol-coherence-override.xml) file as follows:
<!--
Example using tangosol-coherence-override.xml
-->
<coherence>
<cluster-config>
<services>
<!--
id value must match what's in tangosol-coherence.xml for DistributedCache service
-->
<service id="3">
<init-params>
<init-param id="4">
<param-name>local-storage</param-name>
<param-value system-property="tangosol.coherence.distributed.localstorage">false</param-value>
</init-param>
</init-params>
</service>
</services>
</cluster-config>
</coherence>
At least one storage-enabled JVM must be started before any storage-disabled clients access the cache.
Network Protocols
Overview
Coherence uses TCMP, a clustered IP-based protocol, for server discovery, cluster management, service provisioning and data transmission. To ensure true scalability, the TCMP protocol is completely asychronous, meaning that communication is never blocking, even when many threads on a server are communicating at the same time. Further, the asynchronous nature also means that the latency of the network (for example, on a routed network between two different sites) does not affect cluster throughput, although it will affect the speed of certain operations.
TCMP uses a combination of UDP/IP multicast, UDP/IP unicast and TCP/IP as follows:
- Multicast
- Cluster discovery: Is there a cluster already running that a new member can join?
- Cluster heartbeat: The most senior member in the cluster issues a periodic heartbeat via multi-cast; the rate is configurable and defaults to once per second.
- Message delivery: Messages that need to be delivered to multiple cluster members will often be sent via multicast, instead of unicasting the message one time to each member.
- Unicast
- Direct member-to-member ("point-to-point") communication, including messages, asynchronous acknowledgements (ACKs), asynchronous negative acknowledgements (NACKs) and peer-to-peer heartbeats.
- Under some circumstances, a message may be sent via unicast even if the message is directed to multiple members. This is done to shape traffic flow and to reduce CPU load in very large clusters.
- TCP
- An optional TCP/IP ring is used as an additional "death detection" mechanism, to differentiate between actual node failure and an unresponsive node, such as when a JVM conducts a full GC.
- TCP/IP is not used as a data transfer mechanism due to the intrinsic overhead of the protocol and its synchronous nature.
Protocol Reliability
The TCMP protocol provides fully reliable, in-order delivery of all messages. Since the underlying UDP/IP protocol does not provide for either reliable or in-order delivery, TCMP utilizes a queued, fully asynchronous ACK- and NACK-based mechanism for reliable delivery of messages, with unique integral identity for guaranteed ordering of messages.
Protocol Resource Utilization
The TCMP protocol requires only two UDP/IP sockets (one multicast, one unicast) and six threads per JVM, regardless of the cluster size. This is a key element in the scalability of Coherence, in that regardless of the number of servers, each node in the cluster can still communicate either point-to-point or with collections of cluster members without requiring additional network connections.
The optional TCP/IP ring will use a few additional TCP/IP sockets, and a total of one additional thread.
Protocol Tunability
The TCMP protocol is very tunable to take advantage of specific network topologies, or to add tolerance for low-bandwidth and/or high-latency segments in a geographically distributed cluster. Coherence comes with a pre-set configuration, some of which is dynamically self-configuring at runtime, but all attributes of TCMP can be overridden and locked down for deployment purposes.
Multicast Scope
Multicast UDP/IP packets are configured with a time-to-live value (TTL) that designates how far those packets can travel on a network. The TTL is expressed in terms of how many "hops" a packet will survive; each network interface, router and managed switch is considered one hop. Coherence provides a TTL setting to limit the scope of multicast messages.
Disabling Multicast
In most WAN environments, and some LAN environments, multicast traffic is disallowed. To prevent Coherence from using multicast, configure a list of well-known-addresses (WKA). This will disable multicast discovery, and also disable multicast for all data transfer. Coherence is designed to use point-to-point communication as much as possible, so most application profiles will not see a substantial performance impact.
Patterns
Bulk Loading and Processing with Coherence
Bulk Writing to a Cache
A common scenario when using Coherence is to pre populate a cache before the application makes use of the cache. A simple way to do this would be:
public static void bulkLoad(NamedCache cache, Connection conn)
{
Statement s;
ResultSet rs;
try
{
s = conn.createStatement();
rs = s.executeQuery("select key, value from table");
while (rs.next())
{
Integer key = new Integer(rs.getInt(1));
String value = rs.getString(2);
cache.put(key, value);
}
...
}
catch (SQLException e)
{...}
}
This works, but each call to put may result in network traffic, especially for partitioned and replicated caches. Additionally, each call to put will return the object it just replaced in the cache (per the java.util.Map interface) which will add more unnecessary overhead. This can be made much more efficient by using putAll instead:
public static void bulkLoad(NamedCache cache, Connection conn)
{
Statement s;
ResultSet rs;
Map buffer = new HashMap();
try
{
int count = 0;
s = conn.createStatement();
rs = s.executeQuery("select key, value from table");
while (rs.next())
{
Integer key = new Integer(rs.getInt(1));
String value = rs.getString(2);
buffer.put(key, value);
if ((count++ % 1000) == 0)
{
cache.putAll(buffer);
buffer.clear();
}
}
if (!buffer.isEmpty())
{
cache.putAll(buffer);
}
...
}
catch (SQLException e)
{...}
}
Efficient processing of filter results
Coherence provides the ability to query caches based on criteria via the filter API. Here is an example (given entries with integers as keys and strings as values):
NamedCache c = CacheFactory.getCache("test");
Filter query = new LikeFilter(IdentityExtractor.INSTANCE, "c%", '\\', true);
Set results = c.entrySet(query);
for (Iterator i = results.iterator(); i.hasNext();)
{
Map.Entry e = (Map.Entry) i.next();
out("key: "+e.getKey() + ", value: "+e.getValue());
}
This example works for small data sets, but it may encounter problems if the data set is too large, such as running out of heap space. Here is a pattern to process query results in batches to avoid this problem:
public static void performQuery()
{
NamedCache c = CacheFactory.getCache("test");
Filter query = new LikeFilter(IdentityExtractor.INSTANCE, "c%", '\\', true);
Set keys = c.keySet(query);
final int BUFFER_SIZE = 100;
Set buffer = new HashSet(BUFFER_SIZE);
for (Iterator i = keys.iterator(); i.hasNext();)
{
buffer.add(i.next());
if (buffer.size() >= BUFFER_SIZE)
{
Map entries = c.getAll(buffer);
process(entries);
buffer.clear();
}
}
if (!buffer.isEmpty())
{
process(c.getAll(buffer));
}
}
public static void process(Map map)
{
for (Iterator ie = map.entrySet().iterator(); ie.hasNext();)
{
Map.Entry e = (Map.Entry) ie.next();
out("key: "+e.getKey() + ", value: "+e.getValue());
}
}
In this example, all keys for entries that match the filter are returned, but only BUFFER_SIZE (in this case, 100) entries are retrieved from the cache at a time.
Note that
LimitFilter can be used to process results in parts, similar to the example above. However LimitFilter is meant for scenarios where the results will be paged, such as in a user interface. It is not an efficient means to process all data in a query result.
A Complete Example
Here is an example program that demonstrates the concepts described above. Note this can be downloaded from the forums.
package com.tangosol.examples;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.net.cache.NearCache;
import com.tangosol.util.Base;
import com.tangosol.util.Filter;
import com.tangosol.util.filter.LikeFilter;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.HashSet;
/**
* This sample application demonstrates the following:
* <ul>
* <li>
* <b>Obtaining a back cache from a near cache for populating a cache.</b>
* Since the near cache holds a limited subset of the data in a cache it is
* more efficient to bulk load data directly into the back cache instead of
* the near cache.
* </li>
* <li>
* <b>Populating a cache in bulk using <tt>putAll</tt>.</b>
* This is more efficient than <tt>put</tt> for a large amount of entries.
* </li>
* <li>
* <b>Executing a filter against a cache and processing the results in bulk.</b>
* This sample issues a query against the cache using a filter. The result is
* a set of keys that represent the query results. Instead of iterating
* through the keys and loading each item individually with a <tt>get</tt>,
* this sample loads entries from the cache in bulk using <tt>getAll</tt> which
* is more efficient.
* </li>
*
* @author cp, pperalta 2007.02.21
*/
public class PagedQuery
extends Base
{
/**
* Command line execution entry point.
*/
public static void main(String[] asArg)
{
NamedCache cacheContacts = CacheFactory.getCache("contacts",
Contact.class.getClassLoader());
populateCache(cacheContacts);
executeFilter(cacheContacts);
CacheFactory.shutdown();
}
/**
* Populate the cache with test data. This example shows how to populate
* the cache a chunk at a time using {@link NamedCache#putAll} which is more
* efficient than {@link NamedCache#put}.
*
* @param cacheDirect the cache to populate. Note that this should <b>not</b>
* be a near cache since that will thrash the cache
* if the load size exceeds the near cache max size.
*/
public static void populateCache(NamedCache cacheDirect)
{
if (cacheDirect.isEmpty())
{
Map mapBuffer = new HashMap();
for (int i = 0; i < 100000; ++i)
{
Contact contact = new Contact();
contact.setName(getRandomName() + ' ' + getRandomName());
contact.setPhone(getRandomPhone());
mapBuffer.put(new Integer(i), contact);
if ((i % 1000) == 0)
{
out("Adding "+mapBuffer.size()+" entries to cache");
cacheDirect.putAll(mapBuffer);
mapBuffer.clear();
}
}
if (!mapBuffer.isEmpty())
{
cacheDirect.putAll(mapBuffer);
}
}
}
/**
* Creates a random name.
*
* @return a random string between 4 to 11 chars long
*/
public static String getRandomName()
{
Random rnd = getRandom();
int cch = 4 + rnd.nextInt(7);
char[] ach = new char[cch];
ach[0] = (char) ('A' + rnd.nextInt(26));
for (int of = 1; of < cch; ++of)
{
ach[of] = (char) ('a' + rnd.nextInt(26));
}
return new String(ach);
}
/**
* Creates a random phone number
*
* @return a random string of integers 10 chars long
*/
public static String getRandomPhone()
{
Random rnd = getRandom();
return "("
+ toDecString(100 + rnd.nextInt(900), 3)
+ ") "
+ toDecString(100 + rnd.nextInt(900), 3)
+ "-"
+ toDecString(10000, 4);
}
/**
* Query the cache and process the results in batches. This example
* shows how to load a chunk at a time using {@link NamedCache#getAll}
* which is more efficient than {@link NamedCache#get}.
*
* @param cacheDirect the cache to issue the query against
*/
private static void executeFilter(NamedCache cacheDirect)
{
Filter query = new LikeFilter("getName", "C%");
final int CHUNK_COUNT = 100;
Set setKeys = cacheDirect.keySet(query);
Set setBuffer = new HashSet();
for (Iterator iter = setKeys.iterator(); iter.hasNext(); )
{
setBuffer.add(iter.next());
if (setBuffer.size() >= CHUNK_COUNT)
{
processContacts(cacheDirect.getAll(setBuffer));
setBuffer.clear();
}
}
if (!setBuffer.isEmpty())
{
processContacts(cacheDirect.getAll(setBuffer));
}
}
/**
* Process the map of contacts. In a real application some sort of
* processing for each map entry would occur. In this example each
* entry is logged to output.
*
* @param map the map of contacts to be processed
*/
public static void processContacts(Map map)
{
out("processing chunk of " + map.size() + " contacts:");
for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); )
{
Map.Entry entry = (Map.Entry) iter.next();
out(" [" + entry.getKey() + "]=" + entry.getValue());
}
}
/**
* Sample object used to populate cache
*/
public static class Contact
extends Base
implements Serializable
{
public Contact() {}
public String getName()
{
return m_sName;
}
public void setName(String sName)
{
m_sName = sName;
}
public String getPhone()
{
return m_sPhone;
}
public void setPhone(String sPhone)
{
m_sPhone = sPhone;
}
public String toString()
{
return "Contact{"
+ "Name=" + getName()
+ ", Phone=" + getPhone()
+ "}";
}
public boolean equals(Object o)
{
if (o instanceof Contact)
{
Contact that = (Contact) o;
return equals(this.getName(), that.getName())
&& equals(this.getPhone(), that.getPhone());
}
return false;
}
public int hashCode()
{
int result;
result = (m_sName != null ? m_sName.hashCode() : 0);
result = 31 * result + (m_sPhone != null ? m_sPhone.hashCode() : 0);
return result;
}
private String m_sName;
private String m_sPhone;
}
}
Here are the steps to running the example:
- Save this file as com/tangosol/examples/PagedQuery.java
- Point the classpath to the Coherence libraries and the current directory
- Compile and run the example
$ export COHERENCE_HOME=[**Coherence install directory**]
$ export CLASSPATH=$COHERENCE_HOME/lib/tangosol.jar:$COHERENCE_HOME/lib/coherence.jar:.
$ javac com/tangosol/examples/PagedQuery.java
$ java com.tangosol.examples.PagedQuery
2007-05-23 12:19:44.156 Oracle Coherence 3.3/387 (thread=main, member=n/a): Loaded operational configuration from
resource "jar:file:/C:/coherence/lib/coherence.jar!/tangosol-coherence.xml"
2007-05-23 12:19:44.171 Oracle Coherence 3.3/387 (thread=main, member=n/a): Loaded operational overrides from
resource "jar:file:/C:/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml"
2007-05-23 12:19:44.171 Oracle Coherence 3.3/387 (thread=main, member=n/a): Optional configuration override
"/tangosol-coherence-override.xml" is not specified
Oracle Coherence Version 3.3/387
Grid Edition: Development mode
Copyright (c) 2000-2007 Oracle. All rights reserved.
2007-05-23 12:19:44.812 Oracle Coherence GE 3.3/387 (thread=Cluster, member=n/a): Service Cluster joined the cluster
with senior service member n/a
2007-05-23 12:19:48.062 Oracle Coherence GE 3.3/387 (thread=Cluster, member=n/a): Created a new cluster with
Member(Id=1, Timestamp=2007-05-23 12:19:44.609, Address=192.168.0.204:8088, MachineId=26828, Edition=Grid Edition,
Mode=Development, CpuCount=2, SocketCount=1) UID=0xC0A800CC00000112B9BC9B6168CC1F98
Adding 1024 entries to cache
Adding 1024 entries to cache
...repeated many times...
Adding 1024 entries to cache
Adding 1024 entries to cache
Adding 1024 entries to cache
processing chunk of 100 contacts:
[25827]=Contact{Name=Cgkyleass Kmknztk, Phone=(285) 452-0000}
[4847]=Contact{Name=Cyedlujlc Ruexrtgla, Phone=(255) 296-0000}
...repeated many times
[33516]=Contact{Name=Cjfwlxa Wsfhrj, Phone=(683) 968-0000}
[71832]=Contact{Name=Clfsyk Dwncpr, Phone=(551) 957-0000}
processing chunk of 100 contacts:
[38789]=Contact{Name=Cezmcxaokf Kwztt, Phone=(725) 575-0000}
[87654]=Contact{Name=Cuxcwtkl Tqxmw, Phone=(244) 521-0000}
...repeated many times
[96164]=Contact{Name=Cfpmbvq Qaxty, Phone=(596) 381-0000}
[29502]=Contact{Name=Cofcdfgzp Nczpdg, Phone=(563) 983-0000}
...
processing chunk of 80 contacts:
[49179]=Contact{Name=Czbjokh Nrinuphmsv, Phone=(140) 353-0000}
[84463]=Contact{Name=Cyidbd Rnria, Phone=(571) 681-0000}
...
[2530]=Contact{Name=Ciazkpbos Awndvrvcd, Phone=(676) 700-0000}
[9371]=Contact{Name=Cpqo Rmdw, Phone=(977) 729-0000}
CacheFactory Spring Integration
All access to Coherence caches and services happen through
CacheFactory static factory methods. These methods (such as getCache) delegate to a
ConfigurableCacheFactory interface, which is pluggable via CacheFactory.setConfigurableCacheFactory or the operational override file (tangosol-coherence-override.xml.)
In the Coherence cache configuration file (coherence-cache-config.xml) hooks are provided for end users to provide their own implementations of Coherence interfaces, such as
CacheStore and
MapListener. This is configured via a class-scheme. Coherence can instantiate these classes in two ways: it can create a new instance via the new operator, or it can invoke a user provided factory method.
For some applications, it would be useful to allow for Coherence to retrieve objects configured in a class-scheme from a Spring BeanFactory instead of creating its own instance. This is especially true for cache servers configured with CacheStore objects running in a stand-alone