« History of Terracotta Virtualization Server | Main | Fun with DSO and Cyclic Barrier »
August 19, 2005
Object Identity, Tradition and DSO - Part 1
posted by pcalThis article is the first in a three-part series of articles which examines how Terracotta DSO preserves object identity and how it is a key differentiator as compared to traditional API-based cache services.
- Part 1 explains what object identity is and why preserving it in a distributed cache is important.
- Part 2 examines how domain modeling suffers when object identity is not preserved.
- Part 3 illustrates how DSO preserves identity in a sample application.
Overview
In traditional clustered cache architectures, the cache is a service that lives inside the JVM. It is exposed to clients via an API, typically some kind of hashmap: get methods to tell the developer code see what's in the cache, set methods to notify the cache when changes occur, and listener callbacks tell other threads when something elsewhere has changed. All of this machinery is necessary because the traditional cache service cannot understand the developer's domain model. The clustered hashmap API was built to bridge the gap between the cache and the domain.
Terracotta's fundamental proposition with Distributed Shared Objects (DSO) is that the bridge is unnecessary: we can fill in the gap. DSO uses bytecode instrumentation to 'understand' the developer's domain objects in a way that traditional clustered caches simply cannot. With Terracotta, data replication ceases to be an API-based service and instead becomes a property of the JVM itself. The result is a distributed computing model that is completely natural to the developer, entirely transparent to the domain model and extremely performant and scalable.
Averting the Identity Crisis
How exactly is this approach better? One key advantage is that Terracotta DSO preserves object identity. In short, this means that "we don't make extra copies of your objects." With Terracotta, there is exactly one instance of a given domain object on a given JVM. This means that Terracotta can be faster (because copying objects is slow) and transparently coherent (because updating multiple copies of the same thing can be difficult or impossible). It also means it's a lot easier for the developer to use, since there is only one instance of an object to keep track of.
By contrast, traditional API-based cache services can't preserve object identity. With them, the cache maintains a canonical copy of a given object. Clients of the cache may 'check out' any number of additional copies of it for their own use. These copies invariably get out of sync, but it's entirely up to the developer to decide how to handle it.
Worse, traditional caches can't even help the developer maintain semantic associations between the copies. Developers are typically forced to layer some kind of primary-key mechanism onto their domain objects. Traditional caches can force Java programmers to think like relational database designers - a very unnatural state of affairs. (Part 2 of this series will return to this theme in more detail; for now, the focus will be on the check in/check out problem).
A Simple Example
To illustrate the importance of preserving object identity, we will use a simple example of an online store. The OnlineStore contains a list of Products (an Inventory) and some arbitrary groupings of those Products (a set of Departments). For now, we'll just consider a store that contains one Product (a USB Mouse) and one Department (Computer Accessories):
Now, consider that we want to have a distributed cache of our OnlineStore across a cluster of three application servers. We want clients on each server (for example, servlets) to be able to have the same view of the OnlineStore. Moreover, we want changes made by clients on one server to be reflected on the other two.
For the sake of this discussion, we are not going to consider persistence; we're focused only on the cache.
The Traditional Approach
As outlined above, the conventional cache-as-service approach involves installing some kind of hashmap structure on each application server, wiring them up, and populating the domain objects. When a client on one of the application servers wants to view some information about the OnlineStore, it retrieves the object from the cache using some kind of get method:
If the client wishes to update anything in the OnlineStore (say, the price of our USB Mouse), they would make the change to the object they previously retrieved from the cache by calling setPrice() on it...
...and then put it back. (Here, the price update is indicated by the color change from red to green in the data set):
At this point, the cache service takes responsibility for transmitting this change to the other servers in our cluster. Because the cache-as-service doesn't understand our domain objects, it has to serialize the entire OnlineStore object tree over the wire.
Here, we already see some of the limitations of traditional cache services. The first problem is that OnlineStore class and every class it uses must implement java.io.Serializable. That may not be much of an issues if we control all of the source code, but it could be a real headache if we're working with a third-party library.
Worse, we are taking a unnecessary performance hit here: we're only changing the price of our USB Mouse, yet we're serializing the entire store. If we ever expand our product line, this could quickly become a serious problem.
(Astute readers will no doubt be complaining by now that this is a foolish design: we should not be placing the entire OnlineStore object in the cache. I fully agree, but as I'll show in Part 2 of this series, things only get worse when we try to cache our domain objects in a more fine-grained manner).
A more subtle problem arises in the case where other clients are also looking at the cached data. The picture below shows what things look like after the cache has finished distributing our change. Notice that 'Client B' previously called get on the cached data - it now has it's own copy. When the cache service updates the data on Client B's server, what happens?
The clustered hashmap gets the price udpate (indicated by the green data set), but the client's view is stale (red) - it still has the old price for our USB mouse. So much for coherence in our distributed cache.
There certainly are ways around this sort of problem. Cache services typically allow clients to register to receive callbacks when asynchronous updates occur. These sort of workarounds invariably result in extra code and convoluted architectures.
So, we have our cache working, but we've burdened our network, burdened our class hierarchy and burdened our developer.
The Natural Approach
Terracotta DSO relieves all of these burdens because it is able to preserve the identity of cached objects. Again, it can do this because data replication with DSO is not merely a service within the JVM; it is a property of the JVM itself. Let's see how things are different with our OnlineStore in the context of DSO.
Here we see that the relationship between the cache and application server has been inverted. The cache is no longer inside the server; rather, the server is now running inside a DSO-enabled JVM. We have our same client and our same OnlineStore domain object, but there is no cache service:
Now, when the client sends a price update to the Store, it simply calls a setPrice method for the USB mouse. That's it. No checking in and checking out from the cache, no get and put methods, no special API. It couldn't be much more natural than that.
So, how does replication happen? Using bytecode instrumentation, DSO is able to detect the call to setPrice and automatically distribute the change to the other two replicas in our cluster:
An important point to note here is that only the changes are being pushed over the wire - in this case, the new price of the USB Mouse. Whereas the clustered hashmap had to push entire object trees across the wire to maintain cache coherence, Terracotta only needs to push the changes. This translates directly into improved performance and scalability.
Moreover, Terracotta can do this with any object: because it doesn't do object serialization, it doesn't require the types in our Store domain to implement java.io.Serializable. Again, this could be of critical importance if we need to use third-party libraries in our OnlineStore.
Note also that 'Client B' in this picture has not 'checked out' a copy of the Store as it did in the previous illustrations. It doesn't have to; there is only one instance of our OnlineStore on that server. Terracotta manages the state replication in a way that is completely transparent to all clients of the OnlineStore. When the price update arrives and the OnlineStore is updated, everyone sees the change immediately:
The price change from the remote VM takes effect exactly as if it had originated in the local VM. With Terracotta DSO, we get complete cache coherence and complete transparency. No callbacks, no extra code, no pain.
Summary
This is what we mean when we say that Terracotta provides natural clustering: natural semantics, natural scalability, and natural coherence.
| Traditional Clustered Hashmap | Terracotta DSO | |
| Caching Semantics | Check in, check out | Domain-natural, transparent |
| Scaling & Performance | Coarse-grained, java.io Serialization | Fine-grained, change-based |
| Coherence Model | Callbacks, complexity | Simplicity, transparency |
Looking Ahead
This article has shown how traditional cache services force you to think about using the cache. In the next article, we'll examine how they can actually have adverse effects on the design of your domain objects. We'll also see how Terracotta's transparent caching supports a more natural approach to the design of domain models.
Comments
this was certainly the problem with the T3 Workspaces.
The way I look at it is that java programs want objects and things that behave like objects, which is why everyone wants an OR DBMS or an O-R mapping technology instead of ORCL or mysql. the same is true for caching: who wants a service for an object cache?
Posted by: rbp at August 20, 2005 09:39 AM
I think Terracota's approach to distributed is an excellent one, but I still think that Terracota's DSO have to be "very smart". For example, if I change the value of five diferent field of an object one after the other, will terracota distribute five updates (which will use 5 * number of server network roundtrips), or ony one batch with the five updates. I wonder how terracota deals with this kind of issue ...
from the top of my head, I'll be tempted to propose the use of metadata (annotations) to aid terracota deal with the granularity of the updates , or Terracota's update distibution mechanism could be made transactional ... or I'll adventure to propose that Terracota DSO could learn, and improve the updates distribution algorithm over time ...
alex6
Posted by: alexei at August 22, 2005 02:19 PM
Hi Alex. Thanks for the comments.
The point about batching updates is well-taken, so well-taken in fact that we already have the solution. :)
The short of it is that we extend the semantics of the 'synchronized' keyword to apply to distributed objects. DSO can recognize a synchronized block as define transactional boundaries on a set of mutations that yo apply to a distributed object. This ensures transactional integrity as well as avoiding the performance issue you describe.
I'd invite you to download our Product Guide which goes into more detail about how this sort of thing works in DSO:
http://www.terracottatech.com/product/release11.html
Regarding annotations and DSO: yes, this is a great idea, and it is something we are thinking about. I'd love to talk to you more about any ideas you have there.
Posted by: Patrick Calahan at August 22, 2005 03:10 PM
Hello there.
I don't understand what 'classic' cache approach are you describing here.
It looks like you imagine a 2-JVM on one machine model, where the client app runs in a separate JVM than the cache server/service. This model clearly can't provide the client code with object identity.
But if you run the client code inside the cache server, I don't see why a get() call is breaking object identity since the client gets the reference to the unique cached object.
Regards,
Horia Muntean
Posted by: Horia Muntean at September 21, 2005 04:51 AM
Hi Horia. Thanks for the comment - see my responses inline below.
> It looks like you imagine a 2-JVM on one
> machine model, where the client app runs in a
> separate JVM than the cache server/service.
> This model clearly can't provide the client
> code with object identity.
Yes, this is very true: preserving identity between objects in different VM's of course makes no sense. The issue I'm trying to highlight here, though is that the 'classic' approach even breaks object identity between objects within a single VM.
The basic problem is that the clustered hashmap typically relies on Java serialization to distribute updates. This means that any change to an entry in the map results in a _new instance_ being placed in the cache. There may be other objects in the same VM which are logically the same (i.e. are .equals() == true), but they cannot get updated (at least not without resorting to some kind of callback mechanism).
> But if you run the client code inside the cache
> server, I don't see why a get() call is breaking
> object identity since the client gets the
> reference to the unique cached object.
In the case of a single thread and a single VM, this is not a problem. But imagine what happens when the object that you get() is updated by some other thread in the VM or from some other VM in the cluster. The updated object that goes into the cache is going to be a different instance from the one that you have.
Posted by: Patrick Calahan at September 21, 2005 09:33 AM
HI,
I read the article and have following question.
What is the difference between your approach and Toplink Cache Synchronization approach?
In Toplink you can setup JMS service between two or more app servers where your application is running. When a client modifies a cache in one app server and commits it, then the changes are notified to other caches via JMS.
-Mani
Posted by: Mani at September 26, 2005 06:25 AM