<div dir="ltr"><br><div class="gmail_extra"><br><br><div class="gmail_quote">On Tue, Jul 29, 2014 at 5:50 PM, Bela Ban <span dir="ltr">&lt;<a href="mailto:bban@redhat.com" target="_blank">bban@redhat.com</a>&gt;</span> wrote:<br>

<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class=""><br>
<br>
On 29/07/14 16:42, Dan Berindei wrote:<br>
&gt; Have you tried regular optimistic/pessimistic transactions as well?<br>
<br>
</div>Yes, in my first impl. but since I&#39;m making only 1 change per request, I<br>
thought a TX is overkill.<br></blockquote><div><br></div><div>You are using txs with TO, right?</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div class=""><br>
&gt; They *should* have less issues with the OOB thread pool than non-tx mode, and<br>
&gt; I&#39;m quite curious how they stack against TO in such a large cluster.<br>
<br>
</div>Why would they have fewer issues with the thread pools ? AIUI, a TX<br>
involves 2 RPCs (PREPARE-COMMIT/ROLLBACK) compared to one when not using<br>
TXs. And we&#39;re sync anyway...<br>
<div class=""><br></div></blockquote><div><br></div><div>Actually, 2 sync RPCs (prepare + commit) and 1 async RPC (tx completion notification). But we only keep the user thread busy across RPCs (unless L1 is enabled), so we actually need less OOB/remote threads.</div>

<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="">
<br>
&gt; On Tue, Jul 29, 2014 at 5:38 PM, Bela Ban &lt;<a href="mailto:bban@redhat.com">bban@redhat.com</a><br>
</div><div><div class="h5">&gt; &lt;mailto:<a href="mailto:bban@redhat.com">bban@redhat.com</a>&gt;&gt; wrote:<br>
&gt;<br>
&gt;     Following up on my own email, I changed the config to use Pedro&#39;s<br>
&gt;     excellent total order implementation:<br>
&gt;<br>
&gt;     &lt;transaction transactionMode=&quot;TRANSACTIONAL&quot;<br>
&gt;     transactionProtocol=&quot;TOTAL_ORDER&quot; lockingMode=&quot;OPTIMISTIC&quot;<br>
&gt;     useEagerLocking=&quot;true&quot; eagerLockSingleNode=&quot;true&quot;&gt;<br>
&gt;                   &lt;recovery enabled=&quot;false&quot;/&gt;<br>
&gt;<br>
&gt;     With 100 nodes and 25 requester threads/node, I did NOT run into any<br>
&gt;     locking issues !<br>
&gt;<br>
&gt;     I could even go up to 200 requester threads/node and the perf was ~<br>
&gt;     7&#39;000-8&#39;000 requests/sec/node. Not too bad !<br>
&gt;<br>
&gt;     This really validates the concept of lockless total-order dissemination<br>
&gt;     of TXs; for the first time, this has been tested on a large(r) scale<br>
&gt;     (previously only on 25 nodes) and IT WORKS ! :-)<br>
&gt;<br>
&gt;     I still believe we should implement my suggested solution for non-TO<br>
&gt;     configs, but short of configuring thread pools of 1000 threads or<br>
&gt;     higher, I hope TO will allow me to finally test a 500 node Infinispan<br>
&gt;     cluster !<br>
&gt;<br>
&gt;<br>
&gt;     On 29/07/14 15:56, Bela Ban wrote:<br>
&gt;      &gt; Hi guys,<br>
&gt;      &gt;<br>
&gt;      &gt; sorry for the long post, but I do think I ran into an important<br>
&gt;     problem<br>
&gt;      &gt; and we need to fix it ... :-)<br>
&gt;      &gt;<br>
&gt;      &gt; I&#39;ve spent the last couple of days running the IspnPerfTest [1]<br>
&gt;     perftest<br>
&gt;      &gt; on Google Compute Engine (GCE), and I&#39;ve run into a problem with<br>
&gt;      &gt; Infinispan. It is a design problem and can be mitigated by sizing<br>
&gt;     thread<br>
&gt;      &gt; pools correctly, but cannot be eliminated entirely.<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; Symptom:<br>
&gt;      &gt; --------<br>
&gt;      &gt; IspnPerfTest has every node in a cluster perform 20&#39;000 requests<br>
&gt;     on keys<br>
&gt;      &gt; in range [1..20000].<br>
&gt;      &gt;<br>
&gt;      &gt; 80% of the requests are reads and 20% writes.<br>
&gt;      &gt;<br>
&gt;      &gt; By default, we have 25 requester threads per node and 100 nodes in a<br>
&gt;      &gt; cluster, so a total of 2500 requester threads.<br>
&gt;      &gt;<br>
&gt;      &gt; The cache used is NON-TRANSACTIONAL / dist-sync / 2 owners:<br>
&gt;      &gt;<br>
&gt;      &gt; &lt;namedCache name=&quot;clusteredCache&quot;&gt;<br>
&gt;      &gt;        &lt;clustering mode=&quot;distribution&quot;&gt;<br>
&gt;      &gt;            &lt;stateTransfer awaitInitialTransfer=&quot;true&quot;/&gt;<br>
&gt;      &gt;            &lt;hash numOwners=&quot;2&quot;/&gt;<br>
&gt;      &gt;            &lt;sync replTimeout=&quot;20000&quot;/&gt;<br>
&gt;      &gt;        &lt;/clustering&gt;<br>
&gt;      &gt;<br>
&gt;      &gt;        &lt;transaction transactionMode=&quot;NON_TRANSACTIONAL&quot;<br>
&gt;      &gt; useEagerLocking=&quot;true&quot;<br>
&gt;      &gt;             eagerLockSingleNode=&quot;true&quot;  /&gt;<br>
&gt;      &gt;        &lt;locking lockAcquisitionTimeout=&quot;5000&quot; concurrencyLevel=&quot;1000&quot;<br>
&gt;      &gt;                 isolationLevel=&quot;READ_COMMITTED&quot;<br>
&gt;     useLockStriping=&quot;false&quot; /&gt;<br>
&gt;      &gt; &lt;/namedCache&gt;<br>
&gt;      &gt;<br>
&gt;      &gt; It has 2 owners, a lock acquisition timeout of 5s and a repl<br>
&gt;     timeout of<br>
&gt;      &gt; 20s. Lock stripting is off, so we have 1 lock per key.<br>
&gt;      &gt;<br>
&gt;      &gt; When I run the test, I always get errors like those below:<br>
&gt;      &gt;<br>
&gt;      &gt; org.infinispan.util.concurrent.TimeoutException: Unable to<br>
&gt;     acquire lock<br>
&gt;      &gt; after [10 seconds] on key [19386] for requestor<br>
&gt;     [Thread[invoker-3,5,main]]!<br>
&gt;      &gt; Lock held by [Thread[OOB-194,ispn-perf-test,m5.1,5,main]]<br>
&gt;      &gt;<br>
&gt;      &gt; and<br>
&gt;      &gt;<br>
&gt;      &gt; org.infinispan.util.concurrent.TimeoutException: Node m8.1 timed out<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; Investigation:<br>
&gt;      &gt; ------------<br>
&gt;      &gt; When I looked at UNICAST3, I saw a lot of missing messages on the<br>
&gt;      &gt; receive side and unacked messages on the send side. This caused me to<br>
&gt;      &gt; look into the (mainly OOB) thread pools and - voila - maxed out !<br>
&gt;      &gt;<br>
&gt;      &gt; I learned from Pedro that the Infinispan internal thread pool (with a<br>
&gt;      &gt; default of 32 threads) can be configured, so I increased it to<br>
&gt;     300 and<br>
&gt;      &gt; increased the OOB pools as well.<br>
&gt;      &gt;<br>
&gt;      &gt; This mitigated the problem somewhat, but when I increased the<br>
&gt;     requester<br>
&gt;      &gt; threads to 100, I had the same problem again. Apparently, the<br>
&gt;     Infinispan<br>
&gt;      &gt; internal thread pool uses a rejection policy of &quot;run&quot; and thus<br>
&gt;     uses the<br>
&gt;      &gt; JGroups (OOB) thread when exhausted.<br>
&gt;      &gt;<br>
&gt;      &gt; I learned (from Pedro and Mircea) that GETs and PUTs work as<br>
&gt;     follows in<br>
&gt;      &gt; dist-sync / 2 owners:<br>
&gt;      &gt; - GETs are sent to the primary and backup owners and the first<br>
&gt;     response<br>
&gt;      &gt; received is returned to the caller. No locks are acquired, so GETs<br>
&gt;      &gt; shouldn&#39;t cause problems.<br>
&gt;      &gt;<br>
&gt;      &gt; - A PUT(K) is sent to the primary owner of K<br>
&gt;      &gt; - The primary owner<br>
&gt;      &gt;        (1) locks K<br>
&gt;      &gt;        (2) updates the backup owner synchronously *while holding<br>
&gt;     the lock*<br>
&gt;      &gt;        (3) releases the lock<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; Hypothesis<br>
&gt;      &gt; ----------<br>
&gt;      &gt; (2) above is done while holding the lock. The sync update of the<br>
&gt;     backup<br>
&gt;      &gt; owner is done with the lock held to guarantee that the primary and<br>
&gt;      &gt; backup owner of K have the same values for K.<br>
&gt;      &gt;<br>
&gt;      &gt; However, the sync update *inside the lock scope* slows things<br>
&gt;     down (can<br>
&gt;      &gt; it also lead to deadlocks?); there&#39;s the risk that the request is<br>
&gt;      &gt; dropped due to a full incoming thread pool, or that the response<br>
&gt;     is not<br>
&gt;      &gt; received because of the same, or that the locking at the backup owner<br>
&gt;      &gt; blocks for some time.<br>
&gt;      &gt;<br>
&gt;      &gt; If we have many threads modifying the same key, then we have a<br>
&gt;     backlog<br>
&gt;      &gt; of locking work against that key. Say we have 100 requester<br>
&gt;     threads and<br>
&gt;      &gt; a 100 node cluster. This means that we have 10&#39;000 threads accessing<br>
&gt;      &gt; keys; with 2&#39;000 writers there&#39;s a big chance that some writers<br>
&gt;     pick the<br>
&gt;      &gt; same key at the same time.<br>
&gt;      &gt;<br>
&gt;      &gt; For example, if we have 100 threads accessing key K and it takes<br>
&gt;     3ms to<br>
&gt;      &gt; replicate K to the backup owner, then the last of the 100 threads<br>
&gt;     waits<br>
&gt;      &gt; ~300ms before it gets a chance to lock K on the primary owner and<br>
&gt;      &gt; replicate it as well.<br>
&gt;      &gt;<br>
&gt;      &gt; Just a small hiccup in sending the PUT to the primary owner,<br>
&gt;     sending the<br>
&gt;      &gt; modification to the backup owner, waitting for the response, or<br>
&gt;     GC, and<br>
&gt;      &gt; the delay will quickly become bigger.<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; Verification<br>
&gt;      &gt; ----------<br>
&gt;      &gt; To verify the above, I set numOwners to 1. This means that the<br>
&gt;     primary<br>
&gt;      &gt; owner of K does *not* send the modification to the backup owner,<br>
&gt;     it only<br>
&gt;      &gt; locks K, modifies K and unlocks K again.<br>
&gt;      &gt;<br>
&gt;      &gt; I ran the IspnPerfTest again on 100 nodes, with 25 requesters, and NO<br>
&gt;      &gt; PROBLEM !<br>
&gt;      &gt;<br>
&gt;      &gt; I then increased the requesters to 100, 150 and 200 and the test<br>
&gt;      &gt; completed flawlessly ! Performance was around *40&#39;000 requests<br>
&gt;     per node<br>
&gt;      &gt; per sec* on 4-core boxes !<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; Root cause<br>
&gt;      &gt; ---------<br>
&gt;      &gt; *******************<br>
&gt;      &gt; The root cause is the sync RPC of K to the backup owner(s) of K while<br>
&gt;      &gt; the primary owner holds the lock for K.<br>
&gt;      &gt; *******************<br>
&gt;      &gt;<br>
&gt;      &gt; This causes a backlog of threads waiting for the lock and that<br>
&gt;     backlog<br>
&gt;      &gt; can grow to exhaust the thread pools. First the Infinispan internal<br>
&gt;      &gt; thread pool, then the JGroups OOB thread pool. The latter causes<br>
&gt;      &gt; retransmissions to get dropped, which compounds the problem...<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; Goal<br>
&gt;      &gt; ----<br>
&gt;      &gt; The goal is to make sure that primary and backup owner(s) of K<br>
&gt;     have the<br>
&gt;      &gt; same value for K.<br>
&gt;      &gt;<br>
&gt;      &gt; Simply sending the modification to the backup owner(s) asynchronously<br>
&gt;      &gt; won&#39;t guarantee this, as modification messages might get<br>
&gt;     processed out<br>
&gt;      &gt; of order as they&#39;re OOB !<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; Suggested solution<br>
&gt;      &gt; ----------------<br>
&gt;      &gt; The modification RPC needs to be invoked *outside of the lock scope*:<br>
&gt;      &gt; - lock K<br>
&gt;      &gt; - modify K<br>
&gt;      &gt; - unlock K<br>
&gt;      &gt; - send modification to backup owner(s) // outside the lock scope<br>
&gt;      &gt;<br>
&gt;      &gt; The primary owner puts the modification of K into a queue from<br>
&gt;     where a<br>
&gt;      &gt; separate thread/task removes it. The thread then invokes the<br>
&gt;     PUT(K) on<br>
&gt;      &gt; the backup owner(s).<br>
&gt;      &gt;<br>
&gt;      &gt; The queue has the modified keys in FIFO order, so the modifications<br>
&gt;      &gt; arrive at the backup owner(s) in the right order.<br>
&gt;      &gt;<br>
&gt;      &gt; This requires that the way GET is implemented changes slightly:<br>
&gt;     instead<br>
&gt;      &gt; of invoking a GET on all owners of K, we only invoke it on the<br>
&gt;     primary<br>
&gt;      &gt; owner, then the next-in-line etc.<br>
&gt;      &gt;<br>
&gt;      &gt; The reason for this is that the backup owner(s) may not yet have<br>
&gt;      &gt; received the modification of K.<br>
&gt;      &gt;<br>
&gt;      &gt; This is a better impl anyway (we discussed this before) becuse it<br>
&gt;      &gt; generates less traffic; in the normal case, all but 1 GET<br>
&gt;     requests are<br>
&gt;      &gt; unnecessary.<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; Improvement<br>
&gt;      &gt; -----------<br>
&gt;      &gt; The above solution can be simplified and even made more efficient.<br>
&gt;      &gt; Re-using concepts from IRAC [2], we can simply store the modified<br>
&gt;     *keys*<br>
&gt;      &gt; in the modification queue. The modification replication thread<br>
&gt;     removes<br>
&gt;      &gt; the key, gets the current value and invokes a PUT/REMOVE on the<br>
&gt;     backup<br>
&gt;      &gt; owner(s).<br>
&gt;      &gt;<br>
&gt;      &gt; Even better: a key is only ever added *once*, so if we have<br>
&gt;     [5,2,17,3],<br>
&gt;      &gt; adding key 2 is a no-op because the processing of key 2 (in second<br>
&gt;      &gt; position in the queue) will fetch the up-to-date value anyway !<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; Misc<br>
&gt;      &gt; ----<br>
&gt;      &gt; - Could we possibly use total order to send the updates in TO ?<br>
&gt;     TBD (Pedro?)<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; Thoughts ?<br>
&gt;      &gt;<br>
&gt;      &gt;<br>
&gt;      &gt; [1] <a href="https://github.com/belaban/IspnPerfTest" target="_blank">https://github.com/belaban/IspnPerfTest</a><br>
&gt;      &gt; [2]<br>
&gt;      &gt;<br>
&gt;     <a href="https://github.com/infinispan/infinispan/wiki/RAC:-Reliable-Asynchronous-Clustering" target="_blank">https://github.com/infinispan/infinispan/wiki/RAC:-Reliable-Asynchronous-Clustering</a><br>
&gt;      &gt;<br>
&gt;<br>
&gt;     --<br>
&gt;     Bela Ban, JGroups lead (<a href="http://www.jgroups.org" target="_blank">http://www.jgroups.org</a>)<br>
&gt;     _______________________________________________<br>
&gt;     infinispan-dev mailing list<br>
</div></div>&gt;     <a href="mailto:infinispan-dev@lists.jboss.org">infinispan-dev@lists.jboss.org</a> &lt;mailto:<a href="mailto:infinispan-dev@lists.jboss.org">infinispan-dev@lists.jboss.org</a>&gt;<br>
&gt;     <a href="https://lists.jboss.org/mailman/listinfo/infinispan-dev" target="_blank">https://lists.jboss.org/mailman/listinfo/infinispan-dev</a><br>
<div class="HOEnZb"><div class="h5">&gt;<br>
&gt;<br>
&gt;<br>
&gt;<br>
&gt; _______________________________________________<br>
&gt; infinispan-dev mailing list<br>
&gt; <a href="mailto:infinispan-dev@lists.jboss.org">infinispan-dev@lists.jboss.org</a><br>
&gt; <a href="https://lists.jboss.org/mailman/listinfo/infinispan-dev" target="_blank">https://lists.jboss.org/mailman/listinfo/infinispan-dev</a><br>
&gt;<br>
<br>
--<br>
Bela Ban, JGroups lead (<a href="http://www.jgroups.org" target="_blank">http://www.jgroups.org</a>)<br>
_______________________________________________<br>
infinispan-dev mailing list<br>
<a href="mailto:infinispan-dev@lists.jboss.org">infinispan-dev@lists.jboss.org</a><br>
<a href="https://lists.jboss.org/mailman/listinfo/infinispan-dev" target="_blank">https://lists.jboss.org/mailman/listinfo/infinispan-dev</a><br>
</div></div></blockquote></div><br></div></div>