[infinispan-dev] DIST-SYNC, put(), a problem and a solution

Bela Ban bban at redhat.com
Tue Jul 29 09:56:06 EDT 2014


Hi guys,

sorry for the long post, but I do think I ran into an important problem 
and we need to fix it ... :-)

I've spent the last couple of days running the IspnPerfTest [1] perftest 
on Google Compute Engine (GCE), and I've run into a problem with 
Infinispan. It is a design problem and can be mitigated by sizing thread 
pools correctly, but cannot be eliminated entirely.


Symptom:
--------
IspnPerfTest has every node in a cluster perform 20'000 requests on keys 
in range [1..20000].

80% of the requests are reads and 20% writes.

By default, we have 25 requester threads per node and 100 nodes in a 
cluster, so a total of 2500 requester threads.

The cache used is NON-TRANSACTIONAL / dist-sync / 2 owners:

<namedCache name="clusteredCache">
      <clustering mode="distribution">
          <stateTransfer awaitInitialTransfer="true"/>
          <hash numOwners="2"/>
          <sync replTimeout="20000"/>
      </clustering>

      <transaction transactionMode="NON_TRANSACTIONAL" 
useEagerLocking="true"
           eagerLockSingleNode="true"  />
      <locking lockAcquisitionTimeout="5000" concurrencyLevel="1000"
               isolationLevel="READ_COMMITTED" useLockStriping="false" />
</namedCache>

It has 2 owners, a lock acquisition timeout of 5s and a repl timeout of 
20s. Lock stripting is off, so we have 1 lock per key.

When I run the test, I always get errors like those below:

org.infinispan.util.concurrent.TimeoutException: Unable to acquire lock 
after [10 seconds] on key [19386] for requestor [Thread[invoker-3,5,main]]!
Lock held by [Thread[OOB-194,ispn-perf-test,m5.1,5,main]]

and

org.infinispan.util.concurrent.TimeoutException: Node m8.1 timed out


Investigation:
------------
When I looked at UNICAST3, I saw a lot of missing messages on the 
receive side and unacked messages on the send side. This caused me to 
look into the (mainly OOB) thread pools and - voila - maxed out !

I learned from Pedro that the Infinispan internal thread pool (with a 
default of 32 threads) can be configured, so I increased it to 300 and 
increased the OOB pools as well.

This mitigated the problem somewhat, but when I increased the requester 
threads to 100, I had the same problem again. Apparently, the Infinispan 
internal thread pool uses a rejection policy of "run" and thus uses the 
JGroups (OOB) thread when exhausted.

I learned (from Pedro and Mircea) that GETs and PUTs work as follows in 
dist-sync / 2 owners:
- GETs are sent to the primary and backup owners and the first response 
received is returned to the caller. No locks are acquired, so GETs 
shouldn't cause problems.

- A PUT(K) is sent to the primary owner of K
- The primary owner
      (1) locks K
      (2) updates the backup owner synchronously *while holding the lock*
      (3) releases the lock


Hypothesis
----------
(2) above is done while holding the lock. The sync update of the backup 
owner is done with the lock held to guarantee that the primary and 
backup owner of K have the same values for K.

However, the sync update *inside the lock scope* slows things down (can 
it also lead to deadlocks?); there's the risk that the request is 
dropped due to a full incoming thread pool, or that the response is not 
received because of the same, or that the locking at the backup owner 
blocks for some time.

If we have many threads modifying the same key, then we have a backlog 
of locking work against that key. Say we have 100 requester threads and 
a 100 node cluster. This means that we have 10'000 threads accessing 
keys; with 2'000 writers there's a big chance that some writers pick the 
same key at the same time.

For example, if we have 100 threads accessing key K and it takes 3ms to 
replicate K to the backup owner, then the last of the 100 threads waits 
~300ms before it gets a chance to lock K on the primary owner and 
replicate it as well.

Just a small hiccup in sending the PUT to the primary owner, sending the 
modification to the backup owner, waitting for the response, or GC, and 
the delay will quickly become bigger.


Verification
----------
To verify the above, I set numOwners to 1. This means that the primary 
owner of K does *not* send the modification to the backup owner, it only 
locks K, modifies K and unlocks K again.

I ran the IspnPerfTest again on 100 nodes, with 25 requesters, and NO 
PROBLEM !

I then increased the requesters to 100, 150 and 200 and the test 
completed flawlessly ! Performance was around *40'000 requests per node 
per sec* on 4-core boxes !


Root cause
---------
*******************
The root cause is the sync RPC of K to the backup owner(s) of K while 
the primary owner holds the lock for K.
*******************

This causes a backlog of threads waiting for the lock and that backlog 
can grow to exhaust the thread pools. First the Infinispan internal 
thread pool, then the JGroups OOB thread pool. The latter causes 
retransmissions to get dropped, which compounds the problem...


Goal
----
The goal is to make sure that primary and backup owner(s) of K have the 
same value for K.

Simply sending the modification to the backup owner(s) asynchronously 
won't guarantee this, as modification messages might get processed out 
of order as they're OOB !


Suggested solution
----------------
The modification RPC needs to be invoked *outside of the lock scope*:
- lock K
- modify K
- unlock K
- send modification to backup owner(s) // outside the lock scope

The primary owner puts the modification of K into a queue from where a 
separate thread/task removes it. The thread then invokes the PUT(K) on 
the backup owner(s).

The queue has the modified keys in FIFO order, so the modifications 
arrive at the backup owner(s) in the right order.

This requires that the way GET is implemented changes slightly: instead 
of invoking a GET on all owners of K, we only invoke it on the primary 
owner, then the next-in-line etc.

The reason for this is that the backup owner(s) may not yet have 
received the modification of K.

This is a better impl anyway (we discussed this before) becuse it 
generates less traffic; in the normal case, all but 1 GET requests are 
unnecessary.



Improvement
-----------
The above solution can be simplified and even made more efficient. 
Re-using concepts from IRAC [2], we can simply store the modified *keys* 
in the modification queue. The modification replication thread removes 
the key, gets the current value and invokes a PUT/REMOVE on the backup 
owner(s).

Even better: a key is only ever added *once*, so if we have [5,2,17,3], 
adding key 2 is a no-op because the processing of key 2 (in second 
position in the queue) will fetch the up-to-date value anyway !


Misc
----
- Could we possibly use total order to send the updates in TO ? TBD (Pedro?)


Thoughts ?


[1] https://github.com/belaban/IspnPerfTest
[2] 
https://github.com/infinispan/infinispan/wiki/RAC:-Reliable-Asynchronous-Clustering

-- 
Bela Ban, JGroups lead (http://www.jgroups.org)


More information about the infinispan-dev mailing list