[Logo] Terracotta Discussion Forums (LEGACY READ-ONLY ARCHIVE)
  [Search] Search   [Recent Topics] Recent Topics   [Members]  Member Listing   [Groups] Back to home page 
[Register] Register / 
[Login] Login 
[Expert]
Can I do this with terracotta ... ?  XML
Forum Index -> Terracotta Platform Go to Page: 1, 2 Next 
Author Message
micha.b

journeyman

Joined: 06/14/2007 14:21:15
Messages: 47
Offline

Hi,

I have some special implementation requirements where I need to know, if and when how to implement it with terracotta ... maybe somebody can give me some short advise in detail :)

Imagine I have 3 servers and each of it is connected to one http client (for simplicity only). On the servers I've implemented something like a cache which contains two entries: key "A" with value A and key "B" with value B. Client 1 on server 1 wants to get notified, when the value for key "A" changes. Client 2 on server 2 and client 3 on server 3 want to get notified, when value for key "B" changes. Later on it could be, that each server observers an separate value or all servers observe the same too.

Now my questions:

1. Is it possible, to synchronize / share on each server only the needed values and if, how can I do it? When I choose the cache itself as a shared root, all the data in the cache would be synchronized on all connected machines, right? This would mean a lot of not nessessary traffic on the machines and more required space then useful.

2. For the "observation" itself I would use the put method for the cache. Is this right?

I hope, somebody understand, what I mean and can me explain what and how to do it in the best way.

Thanx in advance
Michael
tgautier

seraphim

Joined: 06/05/2006 12:19:26
Messages: 1781
Offline

This is definitely doable. I've been meaning to write up a blog about the listener pattern, in particular the listener pattern with respect to clustering and Terracotta. But that's for another day...

The simplest way to imagine the solution is imagine that each of your servers that want to be notified of the change are just threads. In that case how would you create the solution? (incidentally, this is really the basis for solving all problems in Terracotta, since Terracotta simply extends the Java Memory Model across the cluster, nodes are just threads and everything you would use in a single JVM you can use in multiple JVMs).

So the basics would be to use a combination of a listener pattern with a decorator pattern. Something like (note, I've not compiled this):

Code:
 public interface KeyChangedListener<K, V>
 {
   public void keyChanged(K key, V newValue);
 }
 
 public class NotifyingMap<K, V> implements Map<K, V>
 {
   private final Map<K, V> map;
   private final transient Map<K, Set<KeyChangedListener<K, V>>> listeners = null;
 
   public NotifyingMap(Map<K, V> map)
   {
     this.map = map;
   }
  
   public void addListener(K key, KeyChangedListener<K, V> listener) 
   {
     Set<KeyChangedListener> set = null;
 
     synchronized (this) {
       if (listeners == null) {
         listeners = new HashMap<K, Set<KeyChangedListener<K, V>>>();
     }
 
     synchronized (listeners) {
       set  = listeners.get(key);
       if (set == null) {
         set = new HashSet<KeyChangedListener<K, V>>();
         listeners.put(key, set);
       }
     }
 
 
     synchronized (set) {
       set.add(listener);
     }
   }
 
   public void notifyListeners(K key, V value)
   {
     synchronized (this) {
       if (listeners == null) { return; } 
     }
 
     Set<KeyChangedListener<K, V>> set;
 
     synchronized (listeners) {
       set = listeners.get(key);
       if (set == null) { return; }
     }
 
     synchronized (set) {    
       for (KeyChangedListener : listeners) {
         listener.keyChanged(key, value);
       }
     }
   }
 
   public void put(K key, V value)
   {
     notifyListeners(key, value);
     map.put(key, value);
   }
 
   ... (delegate rest of map methods to map)
 }
 


And the corresponding config file would be approximately:

Code:
 <instrumented-classes>
   <include>
     <class-expression>NotifyingMap</class-expression>
     <honor-transient>true</honor-transient>
   </include>
 </instrumented-classes>
 <distributed-methods>
   <method-expression>* NotifyingMap.notifyListeners(..)</method-expression>
 </distributed-methods>
 
[WWW]
tgautier

seraphim

Joined: 06/05/2006 12:19:26
Messages: 1781
Offline

micha.b wrote:

1. Is it possible, to synchronize / share on each server only the needed values and if, how can I do it? When I choose the cache itself as a shared root, all the data in the cache would be synchronized on all connected machines, right? This would mean a lot of not nessessary traffic on the machines and more required space then useful.

 


Actually, Terracotta will automatically take care of minimizing the traffic and memory requirements. This is because only the objects that are utilized by the VM are actually "faulted" in to the local VM heap memory, and thus only the changes for those objects - the ones in use by the VM - are sent to the node.
[WWW]
micha.b

journeyman

Joined: 06/14/2007 14:21:15
Messages: 47
Offline

Hi,

thank you for your initial help. I've modified the code a little bit, but now it compiles well. Just a little additional question to make sure, that I'm on the right way ... The next steps I have to do are:

1. Implementing a wrapper class (embeds the NotifyingMap as member, e.g. nmap)

2. Declaring this member nmap as shared root

Right?

Best Regards
Michael
tgautier

seraphim

Joined: 06/05/2006 12:19:26
Messages: 1781
Offline

Yep you got it! Sorry I forgot to mention that part.
[WWW]
micha.b

journeyman

Joined: 06/14/2007 14:21:15
Messages: 47
Offline

Hi,

unfortunately I get an exception "Could not find the main class. Program will exit.", when I start the first client (server itself is running). Here is an additional error message:

Code:
 2007-06-18 19:54:56,216 INFO - Terracotta 2.4-nightly-rev3777, as of 20070617-180624 (Revision 3777 by cruise@rh4mo0 from 2.4)
 2007-06-18 19:54:57,278 INFO - Configuration loaded from the file at 'D:\workspace-3.2\Dilos\tc-config.xml'.
 2007-06-18 19:54:57,518 INFO - Log file: 'D:\workspace-3.2\Dilos\terracotta\client-logs\terracotta-client.log'.
 java.lang.IllegalStateException: ClassReader.accept() should be called with EXPAND_FRAMES flag
 	at com.tc.asm.commons.LocalVariablesSorter.visitFrame(LocalVariablesSorter.java:169)
 	at com.tc.asm.ClassReader.accept(ClassReader.java:1159)
 	at com.tc.asm.ClassReader.accept(ClassReader.java:394)
 	at com.tc.object.bytecode.hook.impl.DefaultWeavingStrategy.transform(DefaultWeavingStrategy.java:269)
 	at com.tc.object.bytecode.hook.impl.DSOContextImpl.preProcess(DSOContextImpl.java:137)
 	at com.tc.object.bytecode.hook.impl.ClassProcessorHelper.defineClass0Pre(ClassProcessorHelper.java:429)
 	at java.lang.ClassLoader.defineClass(Unknown Source)
 	at java.security.SecureClassLoader.defineClass(Unknown Source)
 	at java.net.URLClassLoader.defineClass(Unknown Source)
 	at java.net.URLClassLoader.access$000(Unknown Source)
 	at java.net.URLClassLoader$1.run(Unknown Source)
 	at java.security.AccessController.doPrivileged(Native Method)
 	at java.net.URLClassLoader.findClass(Unknown Source)
 	at java.lang.ClassLoader.loadClass(Unknown Source)
 	at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
 	at java.lang.ClassLoader.loadClass(Unknown Source)
 	at java.lang.ClassLoader.loadClassInternal(Unknown Source)
 java.lang.IllegalStateException: ClassReader.accept() should be called with EXPAND_FRAMES flag
 	at com.tc.asm.commons.LocalVariablesSorter.visitFrame(LocalVariablesSorter.java:169)
 	at com.tc.asm.ClassReader.accept(ClassReader.java:1159)
 	at com.tc.asm.ClassReader.accept(ClassReader.java:394)
 	at com.tc.object.bytecode.hook.impl.DefaultWeavingStrategy.transform(DefaultWeavingStrategy.java:269)
 	at com.tc.object.bytecode.hook.impl.DSOContextImpl.preProcess(DSOContextImpl.java:137)
 	at com.tc.object.bytecode.hook.impl.ClassProcessorHelper.defineClass0Pre(ClassProcessorHelper.java:429)
 	at java.lang.ClassLoader.defineClass(Unknown Source)
 	at java.security.SecureClassLoader.defineClass(Unknown Source)
 	at java.net.URLClassLoader.defineClass(Unknown Source)
 	at java.net.URLClassLoader.access$000(Unknown Source)
 	at java.net.URLClassLoader$1.run(Unknown Source)
 	at java.security.AccessController.doPrivileged(Native Method)
 	at java.net.URLClassLoader.findClass(Unknown Source)
 	at java.lang.ClassLoader.loadClass(Unknown Source)
 	at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
 	at java.lang.ClassLoader.loadClass(Unknown Source)
 	at java.lang.ClassLoader.loadClassInternal(Unknown Source)
 Exception in thread "main" 
 


Maybe you know from this message, what's wrong? If you need more information, I can provide you also with a log ...

Best Regards
Michael
natorg

neo
[Avatar]
Joined: 05/24/2006 14:32:42
Messages: 2
Offline

This was fixed on June 13 in revision 3673 in the upcoming 2.4 release, and in revision 3675 in trunk. You should be able to use a newer build to move past this problem, I would suggest the latest 2.4 nightly build.

For reference, the bug is listed at https://jira.terracotta.org/jira/browse/CDV-286; the FishEye tab has links to all of the revisions / diffs.

Nat
[Email] [WWW]
micha.b

journeyman

Joined: 06/14/2007 14:21:15
Messages: 47
Offline

Thank you for the response. Unfortunately I have already revision 3777 (eclipse plugin).
tgautier

seraphim

Joined: 06/05/2006 12:19:26
Messages: 1781
Offline

Are you using JDK 1.6? If so could you try 1.5?
[WWW]
micha.b

journeyman

Joined: 06/14/2007 14:21:15
Messages: 47
Offline

If done it already :)

With JDK 1.5 all works fine. Had to modify the above example once more, because I've got another exception: com.tc.object.tx.UnlockedSharedObjectException

I've changed the put method in this way:

Code:
 public V put( K key, V value )
 {
 	notifyListeners( key, value );
 	
 	synchronized ( map )
 	{
 		return map.put( key, value );
 	}
 }
 


... and added an autolock to the whole put-method. Is this correct in this way or have I locked to much here?

Best Regards
Michael
tgautier

seraphim

Joined: 06/05/2006 12:19:26
Messages: 1781
Offline

Nope, that was exactly the right solution!
[WWW]
micha.b

journeyman

Joined: 06/14/2007 14:21:15
Messages: 47
Offline

Hi,

after some short test I'm a little bit confused ...

I've written a short test app around the NotifyingMap and made some performance measurements. The application without Terracotta shows the following results:


Start test cycle 1.
Stop test cycle 1.
Test cycle 1 took 20 milliseconds.

Start test cycle 2.
Stop test cycle 2.
Test cycle 2 took 10 milliseconds.

Start test cycle 3.
Stop test cycle 3.
Test cycle 3 took 20 milliseconds.

Start test cycle 4.
Stop test cycle 4.
Test cycle 4 took 0 milliseconds.
 


With activated terracotta and running MainTest as DSO I get the following results:


Start test cycle 1.
Stop test cycle 1.
Test cycle 1 took 6028 milliseconds.

Start test cycle 2.
Stop test cycle 2.
Test cycle 2 took 2404 milliseconds.

Start test cycle 3.
Stop test cycle 3.
Test cycle 3 took 1522 milliseconds.

Start test cycle 4.
Stop test cycle 4.
Test cycle 4 took 1532 milliseconds.
 


Are the differences between these two cases indeed so enormous? I will post once more the complete code and the config file, if somebody is interested.

Code:
 package cache;
 
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
 public class NotifyingMap< K, V > implements Map< K, V >
 {
 	private final Map< K, V > map;
 
 	private final transient Map< K, Set< KeyChangedListener< K, V > > > listeners;
 	
 	public NotifyingMap( Map< K, V > map )
 	{
 		this.map = map;
 		this.listeners = new HashMap< K, Set< KeyChangedListener< K, V > > >();
 	}
 
 	public void addListener( K key, KeyChangedListener< K, V > listener ) 
 	{
 		Set< KeyChangedListener< K, V > > set = null;
 
 		synchronized ( listeners )
 		{
 			set  = listeners.get( key );
 			
 			if ( set == null )
 			{
 				set = new HashSet< KeyChangedListener< K, V > >();
 				listeners.put( key, set );
 			}
 		}
 		
 		synchronized ( set )
 		{
 			set.add( listener );
 		}
 	}
 
 	public void notifyListeners( K key , V value )
 	{
 		synchronized ( this )
 		{
 			if ( listeners == null )
 			{
 				return;
 			} 
 		}
 
 		Set< KeyChangedListener< K, V > > set;
 
 		synchronized ( listeners )
 		{
 			set = listeners.get( key );
 			if ( set == null )
 			{
 				return;
 			}
 		}
 
 		synchronized ( set )
 		{
 			for ( KeyChangedListener< K, V > listener : set )
 			{
 				listener.keyChanged( key, value );
 			}
 		}
 	}
 	 
 	public V put( K key, V value )
 	{
 		notifyListeners( key, value );
 		
 		synchronized ( map )
 		{
 			return map.put( key, value );
 		}
 	}
 
 	public void clear()
 	{
 		map.clear();
 	}
 
 	public boolean containsKey( Object key )
 	{
 		return map.containsKey( key );
 	}
 
 	public boolean containsValue( Object value )
 	{
 		return map.containsValue( value );
 	}
 
 	public Set< java.util.Map.Entry< K, V > > entrySet()
 	{
 		return map.entrySet();
 	}
 
 	public V get( Object key )
 	{
 		return map.get( key );
 	}
 
 	public boolean isEmpty()
 	{
 		return map.isEmpty();
 	}
 
 	public Set<K> keySet()
 	{
 		return map.keySet();
 	}
 
 	public void putAll( Map<? extends K, ? extends V > m )
 	{
 		map.putAll( m );
 	}
 
 	public V remove( Object key )
 	{
 		return map.remove( key );
 	}
 
 	public int size()
 	{
 		return map.size();
 	}
 
 	public Collection< V > values()
 	{
 		return map.values();
 	}
 }
 


Code:
 package cache;
 
 import java.util.HashMap;
 
 public class MainTest
 {
 
 	private NotifyingMap< String, String > mMap;
 	
 	public MainTest()
 	{
 		mMap = new NotifyingMap< String, String >( new HashMap< String, String >() );
 	}
 	
 	public void addCacheEntry( String pKey, String pValue )
 	{
 		mMap.put( pKey, pValue );
 	}
 	
 	public void changeCacheEntry( String pKey, String pValue )
 	{
 		mMap.put( pKey, pValue );
 	}
 	
 	/**
 	 * @param args
 	 */
 	public static void main( String[] args )
 	{
 		MainTest tM = new MainTest();
 		
 		System.out.println( "Start test cycle 1." );
 		long tStart = System.currentTimeMillis();
 		for ( int i = 0; i < 1200; i++ )
 		{
 			tM.addCacheEntry( Integer.toString( i ), Integer.toString( i ) );
 		}
 		long tStop = System.currentTimeMillis();
 		System.out.println( "Stop test cycle 1." );
 		System.out.println( "Test cycle 1 took " + ( tStop - tStart ) + " milliseconds." );
 
 		System.out.println();
 		System.out.println( "Start test cycle 2." );
 		tStart = System.currentTimeMillis();
 		for ( int i = 0; i < 1200; i++ )
 		{
 			tM.changeCacheEntry( Integer.toString( i ), Integer.toString( i ) );
 		}
 		tStop = System.currentTimeMillis();
 		System.out.println( "Stop test cycle 2." );
 		System.out.println( "Test cycle 2 took " + ( tStop - tStart ) + " milliseconds." );
 		
 		System.out.println();
 		System.out.println( "Start test cycle 3." );
 		tStart = System.currentTimeMillis();
 		for ( int i = 1200; i < 2400; i++ )
 		{
 			tM.addCacheEntry( Integer.toString( i ), Integer.toString( i ) );
 		}
 		tStop = System.currentTimeMillis();
 		System.out.println( "Stop test cycle 3." );
 		System.out.println( "Test cycle 3 took " + ( tStop - tStart ) + " milliseconds." );
 
 		System.out.println();
 		System.out.println( "Start test cycle 4." );
 		tStart = System.currentTimeMillis();
 		for ( int i = 1200; i < 2400; i++ )
 		{
 			tM.changeCacheEntry( Integer.toString( i ), Integer.toString( i ) );
 		}
 		tStop = System.currentTimeMillis();
 		System.out.println( "Stop test cycle 4." );
 		System.out.println( "Test cycle 4 took " + ( tStop - tStart ) + " milliseconds." );
 	}
 }
 


Code:
 <con:tc-config xmlns:con="http://www.terracotta.org/config">
   <servers>
     <server host="%i" name="localhost">
       <dso-port>9510</dso-port>
       <jmx-port>9520</jmx-port>
       <data>terracotta/server-data</data>
       <logs>terracotta/server-logs</logs>
     </server>
   </servers>
   <clients>
     <logs>terracotta/client-logs</logs>
   </clients>
   <application>
     <dso>
       <instrumented-classes>
         <include>
           <class-expression>cache.NotifyingMap</class-expression>
           <honor-transient>true</honor-transient>
         </include>
         <include>
           <class-expression>java.util.Map+</class-expression>
         </include>
       </instrumented-classes>
       <distributed-methods>
         <method-expression>void cache.NotifyingMap.notifyListeners(*, *)</method-expression>
       </distributed-methods>
       <roots>
         <root>
           <field-name>cache.MainTest.mMap</field-name>
         </root>
       </roots>
       <locks>
         <autolock>
           <method-expression>* cache.NotifyingMap.put(*, *)</method-expression>
           <lock-level>write</lock-level>
         </autolock>
       </locks>
       <additional-boot-jar-classes>
         <include>java.util.Map</include>
       </additional-boot-jar-classes>
     </dso>
   </application>
 </con:tc-config>
 


Best Regards
Michael
micha.b

journeyman

Joined: 06/14/2007 14:21:15
Messages: 47
Offline

Hi,

thank you for your response. I think, the rest of the conversation could be to special for the rest of the community, so if you agree, I would send you a pm with more detailed data about the underlying process.

Best Regards
Michael
tgautier

seraphim

Joined: 06/05/2006 12:19:26
Messages: 1781
Offline

Hi Michael.

I've been running the code. The first obvious optimization is that the test is loading the cache with 1200 objects. Is this expected behavior (loading one by one) or can it be batched? Calling put(..) on the map 1200 times acquires 1200 locks...

My initial code was never meant to be performance tested, so now you are making me go back and think about it from this test case perspective... :)

I'll see if I can't come up with something faster.

The other thing to consider here is that to perform valid tests we really need to ensure that the TC Server is on a different node - this is because you are simply running a "how fast can it possibly go" test and this will be capped very quickly by the CPU if both the server and the client test are on the same machine.

In addition, the test is very serial. In reality I would expect these operations to be performed in parallel. We really want to know how many operations can be performed in a given time period using many threads (VMs) not a single thread which doesn't give Terracotta a chance to optimize and batch the reads/writes.

[WWW]
micha.b

journeyman

Joined: 06/14/2007 14:21:15
Messages: 47
Offline

Hi,

I've posted a response parallel to your, but you changed your post so that I am now upon your message :)

Best Regards
Michael

 
Forum Index -> Terracotta Platform Go to Page: 1, 2 Next 
Go to:   
Powered by JForum 2.1.7 © JForum Team