<!-- 
RSS generated by JIRA (8.20.10#820010-sha1:ace47f9899e9ee25d7157d59aa17ab06aee30d3d) at Wed Feb 07 19:53:33 UTC 2024

It is possible to restrict the fields that are returned in this document by specifying the 'field' parameter in your request.
For example, to request only the issue key and summary append 'field=key&field=summary' to the URL of your request.
-->
<rss version="0.92" >
<channel>
    <title>OpenDaylight JIRA</title>
    <link>https://jira.opendaylight.org</link>
    <description>This file is an XML representation of an issue</description>
    <language>en-us</language>    <build-info>
        <version>8.20.10</version>
        <build-number>820010</build-number>
        <build-date>22-06-2022</build-date>
    </build-info>


<item>
            <title>[CONTROLLER-651] Multi-thread parts of WriteTx commit process</title>
                <link>https://jira.opendaylight.org/browse/CONTROLLER-651</link>
                <project id="10113" key="CONTROLLER">controller</project>
                    <description>&lt;p&gt;Currently the WriteTx commit process is all single-theaded. At scale this may be a bottleneck. We should try to off-load and multi-thread at least parts of the commit so as to avoid blocking the commit thread and also gain some parallelism. &lt;/p&gt;

&lt;p&gt;One part is the notification of DataChangeListeners. Currently the ThreePhaseCommitImpl calls DataChangeListeners on an executor that executes tasks in the same thread as the caller so DataChangeListeners block the commit thread. This was done because DataChangeListeners need to be notified of change events in the order that they occur. &lt;/p&gt;

&lt;p&gt;However we can queue events per DataChangeListener and dispatch them serially on separate threads. I have prototyped a QueuedNotificationManager to do this. In addition, the QueuedNotificationManager optimizes its memory footprint by only allocating and maintaining a queue and executor task for a listener when there are pending notifications. Once all notifications have been dispatched, the queue and task are discarded.&lt;/p&gt;

&lt;p&gt;Another part of the commit process that can be off-loaded is the notification of the ListenableFuture that&apos;s returned from the submit call. Typically, clients will use Futures#addCallBack so as not to block on the ListenableFuture. The default behavior of Futures#addCallBack is to use MoreExecutors#sameThreadExecutor which results in the client callback running on the commit thread. While it is recommended that clients not do any expensive processing and/or block, I think it&apos;s fragile to rely on that. One scenario: if a client does a commit in an RPC call and sets the RPC result Future in the commit callback, if the caller of the RPC also uses Futures#addCallBack with MoreExecutors#sameThreadExecutor, then it will also run in the commit thread and so on. &lt;/p&gt;

&lt;p&gt;To off-load commit ListenableFuture callbacks, I have prototyped an AsyncNotifyingListeningExecutorService class that uses an AsyncNotifyingListenableFutureTask that takes an Executor and notifies registered ListenableFuture Runnables on the Executor when the task completes and the future result is set. ListenableFuture#addListener also takes an Executor which, by default, is MoreExecutors#sameThreadExecutor. This Executor is still used but if the AsyncNotifyingListenableFutureTask detects that the listener Runnable task will run on the same thread that completed the AsyncNotifyingListenableFutureTask, then it is off-loaded to the other Executor.&lt;/p&gt;</description>
                <environment>&lt;p&gt;Operating System: All&lt;br/&gt;
Platform: All&lt;/p&gt;</environment>
        <key id="25205">CONTROLLER-651</key>
            <summary>Multi-thread parts of WriteTx commit process</summary>
                <type id="10100" iconUrl="https://jira.opendaylight.org/secure/viewavatar?size=xsmall&amp;avatarId=10310&amp;avatarType=issuetype">Improvement</type>
                                                <status id="5" iconUrl="https://jira.opendaylight.org/images/icons/statuses/resolved.png" description="A resolution has been taken, and it is awaiting verification by reporter. From here issues are either reopened, or are closed.">Resolved</status>
                    <statusCategory id="3" key="done" colorName="green"/>
                                    <resolution id="10000">Done</resolution>
                                        <assignee username="tpantelis">Tom Pantelis</assignee>
                                    <reporter username="tpantelis">Tom Pantelis</reporter>
                        <labels>
                    </labels>
                <created>Sat, 26 Jul 2014 14:48:55 +0000</created>
                <updated>Mon, 18 Aug 2014 20:22:27 +0000</updated>
                            <resolved>Mon, 18 Aug 2014 20:22:27 +0000</resolved>
                                    <version>Helium</version>
                                                    <component>mdsal</component>
                        <due></due>
                            <votes>0</votes>
                                    <watches>4</watches>
                                                                                                                <comments>
                            <comment id="48768" author="rovarga" created="Thu, 31 Jul 2014 07:22:05 +0000"  >&lt;p&gt;I&apos;d like to understand the threading model of QueuedNotificationManager &amp;#8211; is it a thread per listener? The description is very vague in this regard.&lt;/p&gt;

&lt;p&gt;I did not quite get mechanics of the Future notification part. Can you describe it somewhere in the wiki?&lt;/p&gt;</comment>
                            <comment id="48769" author="tpantelis" created="Thu, 31 Jul 2014 12:00:08 +0000"  >&lt;p&gt;(In reply to Robert Varga from comment #1)&lt;br/&gt;
&amp;gt; I&apos;d like to understand the threading model of QueuedNotificationManager &amp;#8211;&lt;br/&gt;
&amp;gt; is it a thread per listener? The description is very vague in this regard.&lt;/p&gt;

&lt;p&gt;I have pushed &lt;a href=&quot;https://git.opendaylight.org/gerrit/#/c/9305/&quot; class=&quot;external-link&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener&quot;&gt;https://git.opendaylight.org/gerrit/#/c/9305/&lt;/a&gt; with the new concurrent classes.&lt;/p&gt;

&lt;p&gt;There is a thread per listener but it&apos;s dynamic to conserve resources.&lt;/p&gt;

&lt;p&gt;Here&apos;s the sequence:&lt;/p&gt;

&lt;p&gt;1) The commit thread creates a DataChangeEvent and and summits it to the QueuedNotificationManager (QNM).&lt;/p&gt;

&lt;p&gt;2) The QNM contains a map of listener -&amp;gt; queue. The QNM looks up in the map. There&apos;s no entry yet for the listener so it creates a queue containing the event and submits a task to an executor.&lt;/p&gt;

&lt;p&gt;3) The executor task runs and loops and dispatches events until the queue is empty.&lt;/p&gt;

&lt;p&gt;4) Once he queue is empty, the task is done so it removes the listener queue entry from the listener map and exits. &lt;/p&gt;

&lt;p&gt;5) The next time an event occurs for the listener, repeat 2) - 4), i.e. a new queue is created and a new task submitted.&lt;/p&gt;

&lt;p&gt;If another event occurs during 3), the QNM will find an existing queue for the listener in the map and it just offers the event. &lt;/p&gt;

&lt;p&gt;So in this manner, there is a separate queue and thread for each listener and events are dispatched serially to each listener and concurrently wrt other listeners. When there no events to dispatch to a listener, eventually it&apos;s queue, task and thread are cleaned up.&lt;/p&gt;

&lt;p&gt;&amp;gt; &lt;br/&gt;
&amp;gt; I did not quite get mechanics of the Future notification part. Can you&lt;br/&gt;
&amp;gt; describe it somewhere in the wiki?&lt;/p&gt;

&lt;p&gt;Which wiki are you referring (there&apos;s like thousands)?&lt;/p&gt;

&lt;p&gt;It&apos;s probably easier to discuss this on a meeting or hangout. Email me when you&apos;re available.&lt;/p&gt;</comment>
                            <comment id="48770" author="tpantelis" created="Thu, 31 Jul 2014 14:30:31 +0000"  >&lt;p&gt;Re: the Future notification, the DOMDataCommitCoordinatorImpl uses a ListeningExecutorService backed by a single-threaded executor. When #submit is called it submits a Callable task which returns a ListenableFuture back to the client.&lt;/p&gt;

&lt;p&gt;Clients will typically used Futures#addCallback to be notified asynchronously when the Future is complete (at least that&apos;s what&apos;s recommended). Futures#addCallback takes an optional Executor on which to invoke the listener&apos;s callback. This defaults to MoreExecutors#sameThreadExecutor and is what clients will commonly use.&lt;/p&gt;

&lt;p&gt;The Callable task is wrapped by an internal Runnable by the executor. When the task executes, the Runnable calls the Callable and, when complete, it sets the Future result (or exception). The derived ListenableFuture then submits its client listener callback task(s) on their respective Executors. If a client&apos;s Executor is one that runs in the same thread, then it will run on the single commit thread and will block it until it returns. This is the part I&apos;m proposing to alleviate.&lt;/p&gt;

&lt;p&gt;As I outlined in the bug desc, I think&apos;s it&apos;s fragile to rely on client callbacks not doing any expensive processing and/or block. Also, we lose parallelism if it&apos;s single-threaded.&lt;/p&gt;

&lt;p&gt;So I prototyped an AsyncNotifyingListeningExecutorService class that will use a specified Executor to off-load client ListenableFuture callbacks if the Executor the client registered with will run in the same thread on which the Future was completed.&lt;/p&gt;</comment>
                            <comment id="48771" author="tpantelis" created="Thu, 31 Jul 2014 15:06:13 +0000"  >&lt;p&gt;In addition, the QueueNotificationMgr and AsyncNotifyingListeningExecutorService will use executors with bounded queues and with a RejectedExecutionHandler that blocks if the queue is full. I&apos;ve been debating which technique to use for blocking, either the CallerRunsPolicy RejectedExecutionHandler or one that does a blocking put to the queue. I&apos;m favoring CallerRunsPolicy because it allows the caller (i.e. single commit thread) to eventually continue although degradedly (assuming the notification task competes).&lt;/p&gt;

&lt;p&gt;The single-threaded commit executor&apos;s queue is currently unbounded. Since the commit thread could still block on listener notifications, we should bound the queue (say capacity 5000). Then the question is what do we do if the commit executor queue gets full? We can&apos;t use CallerRunsPolicy because that would allow for out-of-order commits. We could use a RejectedExecutionHandler that does a blocking put to the queue to help relieve the back pressure but then what happens to the caller of that caller and so on.&lt;br/&gt;
I think at some point somebody has to give up.&lt;/p&gt;

&lt;p&gt;In discussing with Colin, we think it&apos;s reasonable to just fail the commit and let the caller deal with that. That case should be extreme - if we get to the point where that many commits are backed up there&apos;s something seriously wrong, probably deadlock or endless loop somewhere.&lt;/p&gt;</comment>
                            <comment id="48772" author="rovarga" created="Thu, 31 Jul 2014 19:08:43 +0000"  >&lt;p&gt;I agree on the general mechanics as a first step.&lt;/p&gt;

&lt;p&gt;We cannot use the CallerRuns policy for event notifications because that would violate event delivery ordering and &quot;logically single-threaded&quot; contract of DataChangeListener.&lt;/p&gt;

&lt;p&gt;The correct way of doing things here is to use RejectedExecutionHandler on the queue and reserve an entry during pre-commit phase. That way the commit process will fail predictably and leave the datastore in a consistent state if we are running low.&lt;/p&gt;

&lt;p&gt;As an incremental (and maybe optional, this is API contract question) improvement, we can maintain a queue-per-listener (with multiple queues serviced by a single thread). If we are not required to maintain event boundaries, we can perform very cheap (== cost of building a transaction payload) state compression on egress. That will have the effect of coalescing modifications into larger transactions, thus lowering the transactional activity without impairing throughput, as you essentially maintain a single entry.&lt;/p&gt;</comment>
                            <comment id="48773" author="tpantelis" created="Thu, 31 Jul 2014 19:55:02 +0000"  >&lt;p&gt;(In reply to Robert Varga from comment #5)&lt;br/&gt;
&amp;gt; I agree on the general mechanics as a first step.&lt;br/&gt;
&amp;gt; &lt;br/&gt;
&amp;gt; We cannot use the CallerRuns policy for event notifications because that&lt;br/&gt;
&amp;gt; would violate event delivery ordering and &quot;logically single-threaded&quot;&lt;br/&gt;
&amp;gt; contract of DataChangeListener.&lt;br/&gt;
&amp;gt; &lt;/p&gt;

&lt;p&gt;  There&apos;s actually 2 queues in question here, one for the executor tasks and the other per listener. If the QNM calls the executor to submit a notification task, that means there either isn&apos;t a current notification task running and dispatching events for that listener or there is a current notification task but it has finished dispatching and is in the process of exiting. For the latter, there is a small window between the time it sees an empty queue and is breaking out of its loop and before it removes itself from the cache prior to exiting. Either way it means it&apos;s starting a new task for the listener and all previous events have been dispatched. So I think CallerRunsPolicy is valid here. Please take a look at the code I submitted. &lt;/p&gt;

&lt;p&gt;&amp;gt; The correct way of doing things here is to use RejectedExecutionHandler on&lt;br/&gt;
&amp;gt; the queue and reserve an entry during pre-commit phase. That way the commit&lt;br/&gt;
&amp;gt; process will fail predictably and leave the datastore in a consistent state&lt;br/&gt;
&amp;gt; if we are running low.&lt;br/&gt;
&amp;gt; &lt;/p&gt;

&lt;p&gt;Which queue are you referring to? The single-threaded commit queue? If so, I&apos;m not sure what you mean by &quot;reserve an entry during pre-commit phase&quot;. The way I implemented commit executor failure is by catching the RejectedExecutionException from the submit in DOMDataCommitCoordinatorImpl and returning a failed Future with TransactionCommitFailedException. So that writeTx is dropped and not even attempted to be committed. That shouldn&apos;t affect the datastore in any way unless I&apos;m missing something. The controller code is in a draft: &lt;a href=&quot;https://git.opendaylight.org/gerrit/#/c/9422/&quot; class=&quot;external-link&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener&quot;&gt;https://git.opendaylight.org/gerrit/#/c/9422/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&amp;gt; As an incremental (and maybe optional, this is API contract question)&lt;br/&gt;
&amp;gt; improvement, we can maintain a queue-per-listener (with multiple queues&lt;br/&gt;
&amp;gt; serviced by a single thread). If we are not required to maintain event&lt;br/&gt;
&amp;gt; boundaries, we can perform very cheap (== cost of building a transaction&lt;br/&gt;
&amp;gt; payload) state compression on egress. That will have the effect of&lt;br/&gt;
&amp;gt; coalescing modifications into larger transactions, thus lowering the&lt;br/&gt;
&amp;gt; transactional activity without impairing throughput, as you essentially&lt;br/&gt;
&amp;gt; maintain a single entry.&lt;/p&gt;</comment>
                            <comment id="48774" author="tpantelis" created="Wed, 13 Aug 2014 03:10:50 +0000"  >&lt;p&gt;The functional changes have been merged.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://git.opendaylight.org/gerrit/#/c/9905/&quot; class=&quot;external-link&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener&quot;&gt;https://git.opendaylight.org/gerrit/#/c/9905/&lt;/a&gt; is a follow-up patch to convert to use the config system instead of system properties for executor config params.&lt;/p&gt;</comment>
                            <comment id="48775" author="tpantelis" created="Mon, 18 Aug 2014 20:22:27 +0000"  >&lt;p&gt;Follow-up patch &lt;a href=&quot;https://git.opendaylight.org/gerrit/#/c/9905/&quot; class=&quot;external-link&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener&quot;&gt;https://git.opendaylight.org/gerrit/#/c/9905/&lt;/a&gt; has been merged.&lt;/p&gt;</comment>
                    </comments>
                    <attachments>
                    </attachments>
                <subtasks>
                    </subtasks>
                <customfields>
                                                                            <customfield id="customfield_11400" key="com.atlassian.jira.plugins.jira-development-integration-plugin:devsummary">
                        <customfieldname>Development</customfieldname>
                        <customfieldvalues>
                            
                        </customfieldvalues>
                    </customfield>
                                                                                                                        <customfield id="customfield_10208" key="com.atlassian.jira.plugin.system.customfieldtypes:textfield">
                        <customfieldname>External issue ID</customfieldname>
                        <customfieldvalues>
                            <customfieldvalue>1430</customfieldvalue>

                        </customfieldvalues>
                    </customfield>
                                                                <customfield id="customfield_10201" key="com.atlassian.jira.plugin.system.customfieldtypes:url">
                        <customfieldname>External issue URL</customfieldname>
                        <customfieldvalues>
                            <customfieldvalue><![CDATA[https://bugs.opendaylight.org/show_bug.cgi?id=1430]]></customfieldvalue>

                        </customfieldvalues>
                    </customfield>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                <customfield id="customfield_10000" key="com.pyxis.greenhopper.jira:gh-lexo-rank">
                        <customfieldname>Rank</customfieldname>
                        <customfieldvalues>
                            <customfieldvalue>0|i02lpr:</customfieldvalue>

                        </customfieldvalues>
                    </customfield>
                                                                                                                                                                                </customfields>
    </item>
</channel>
</rss>