1 === MD-SAL Data Transactions
3 MD-SAL *Data Broker* provides transactional access to conceptual *data trees*
4 representing configuration and operational state.
6 NOTE: *Data tree* usually represents state of the modeled data, usually this
7 is state of controller, applications and also external systems (network
10 *Transactions* provide *<<_transaction_isolation, stable and isolated view>>*
11 from other currently running transactions. The state of running transaction and
12 underlying data tree is not affected by other concurrently running transactions.
16 Transaction provides only modification capabilities, but does not provide
17 read capabilities. Write-only transaction is allocated using
18 `newWriteOnlyTransaction()`.
20 NOTE: This allows less state tracking for
21 write-only transactions and allows MD-SAL Clustering to optimize
22 internal representation of transaction in cluster.
24 Transaction provides both read and write capabilities. It is allocated using
25 `newReadWriteTransaction()`.
27 Transaction provides stable read-only view based on current data tree.
28 Read-only view is not affected by any subsequent write transactions.
29 Read-only transaction is allocated using `newReadOnlyTransaction()`.
31 NOTE: If an application needs to observe changes itself in data tree, it should use
32 *data tree listeners* instead of read-only transactions and polling data tree.
34 Transactions may be allocated using the *data broker* itself or using
35 *transaction chain*. In the case of *transaction chain*, the new allocated transaction
36 is not based on current state of data tree, but rather on state introduced by
37 previous transaction from the same chain, even if the commit for previous transaction
38 has not yet occurred (but transaction was submitted).
41 ==== Write-Only & Read-Write Transaction
43 Write-Only and Read-Write transactions provide modification capabilities for
44 the conceptual data trees.
46 .Usual workflow for data tree modifications
47 1. application allocates new transactions using `newWriteOnlyTransaction()`
48 or `newReadWriteTransaction()`.
49 2. application <<_modification_of_data_tree,modifies data tree>> using `put`,
50 `merge` and/or `delete`.
51 3. application finishes transaction using <<_submitting_transaction,`submit()`>>,
52 which seals transaction and submits it to be processed.
53 4. application observes the result of the transaction commit using either blocking
54 or asynchronous calls.
56 The *initial state* of the write transaction is a *stable snapshot* of the current
57 data tree state captured when transaction was created and it's state and
58 underlying data tree are not affected by other concurrently running transactions.
60 Write transactions are *isolated* from other concurrent write transactions. All
61 *<<_transaction_local_state,writes are local>>* to the transaction and
62 represents only a *proposal of state change* for data tree and *are not visible*
63 to any other concurrently running transactions (including read-only transactions).
65 The transaction *<<_commit_failure_scenarios,commit may fail>>* due to failing
66 verification of data or concurrent transaction modifying and affected data
67 in an incompatible way.
69 ===== Modification of Data Tree
71 Write-only and read-write transaction provides following methods to modify
78 <T> void put(LogicalDatastoreType store, InstanceIdentifier<T> path, T data);
81 Stores a piece of data at a specified path. This acts as an *add / replace*
82 operation, which is to say that whole subtree will be replaced by the
90 <T> void merge(LogicalDatastoreType store, InstanceIdentifier<T> path, T data);
93 Merges a piece of data with the existing data at a specified path.
94 Any *pre-existing data* which are not explicitly overwritten *will be preserved*.
95 This means that if you store a container, its child subtrees will be merged.
101 void delete(LogicalDatastoreType store, InstanceIdentifier<?> path);
104 Removes a whole subtree from a specified path.
106 ===== Submitting transaction
108 Transaction is submitted to be processed and committed using following method:
112 CheckedFuture<Void,TransactionCommitFailedException> submit();
115 Applications publish the changes proposed in the transaction by calling `submit()`
117 This *seals the transaction* (preventing any further writes using this transaction)
118 and submits it to be processed and applied to global conceptual data tree.
119 The `submit()` method does not block, but rather returns `ListenableFuture`, which
120 will complete successfully once processing of transaction is finished and changes
121 are applied to data tree. If *commit* of data failed, the future will fail with
122 `TransactionFailedException`.
124 Application may listen on commit state asynchronously using `ListenableFuture`.
128 Futures.addCallback( writeTx.submit(), new FutureCallback<Void>() { // <1>
129 public void onSuccess( Void result ) { // <2>
130 LOG.debug("Transaction committed successfully.");
133 public void onFailure( Throwable t ) { // <3>
134 LOG.error("Commit failed.",e);
139 <1> Submits `writeTx` and registers application provided `FutureCallback`
141 <2> Invoked when future completed successfully - transaction `writeTx` was
142 successfully committed to data tree.
143 <3> Invoked when future failed - commit of transaction `writeTx` failed.
144 Supplied exception provides additional details and cause of failure.
146 If application need to block till commit is finished it may use `checkedGet()`
147 to wait till commit is finished.
152 writeTx.submit().checkedGet(); // <1>
153 } catch (TransactionCommitFailedException e) { // <2>
154 LOG.error("Commit failed.",e);
158 <1> Submits `writeTx` and blocks till commit of `writeTx` is finished. If
159 commit fails `TransactionCommitFailedException` will be thrown.
160 <2> Catches `TransactionCommitFailedException` and logs it.
162 ===== Transaction local state
164 Read-Write transactions maintain transaction-local state, which renders all
165 modifications as if they happened, but this is only local to transaction.
167 Reads from the transaction returns data as if the previous modifications in
168 transaction already happened.
170 Let assume initial state of data tree for `PATH` is `A`.
173 ReadWriteTransaction rwTx = broker.newReadWriteTransaction(); // <1>
175 rwRx.read(OPERATIONAL,PATH).get(); // <2>
176 rwRx.put(OPERATIONAL,PATH,B); // <3>
177 rwRx.read(OPERATIONAL,PATH).get(); // <4>
178 rwRx.put(OPERATIONAL,PATH,C); // <5>
179 rwRx.read(OPERATIONAL,PATH).get(); // <6>
182 <1> Allocates new `ReadWriteTransaction`.
183 <2> Read from `rwTx` will return value `A` for `PATH`.
184 <3> Writes value `B` to `PATH` using `rwTx`.
185 <4> Read will return value `B` for `PATH`, since previous write occurred in same
187 <5> Writes value `C` to `PATH` using `rwTx`.
188 <6> Read will return value `C` for `PATH`, since previous write occurred in same
191 ==== Transaction isolation
193 Running (not submitted) transactions are isolated from each other and changes
194 done in one transaction are not observable in other currently running
197 Lets assume initial state of data tree for `PATH` is `A`.
201 ReadOnlyTransaction txRead = broker.newReadOnlyTransaction(); // <1>
202 ReadWriteTransaction txWrite = broker.newReadWriteTransaction(); // <2>
204 txRead.read(OPERATIONAL,PATH).get(); // <3>
205 txWrite.put(OPERATIONAL,PATH,B); // <4>
206 txWrite.read(OPERATIONAL,PATH).get(); // <5>
207 txWrite.submit().get(); // <6>
208 txRead.read(OPERATIONAL,PATH).get(); // <7>
209 txAfterCommit = broker.newReadOnlyTransaction(); // <8>
210 txAfterCommit.read(OPERATIONAL,PATH).get(); // <9>
213 <1> Allocates read only transaction, which is based on data tree which
214 contains value `A` for `PATH`.
215 <2> Allocates read write transaction, which is based on data tree which
216 contains value `A` for `PATH`.
217 <3> Read from read-only transaction returns value `A` for `PATH`.
218 <4> Data tree is updated using read-write transaction, `PATH` contains `B`.
219 Change is not public and only local to transaction.
220 <5> Read from read-write transaction returns value `B` for `PATH`.
221 <6> Submits changes in read-write transaction to be committed to data tree.
222 Once commit will finish, changes will be published and `PATH` will be
223 updated for value `B`. Previously allocated transactions are not affected by
225 <7> Read from previously allocated read-only transaction still returns value `A`
226 for `PATH`, since it provides stable and isolated view.
227 <8> Allocates new read-only transaction, which is based on data tree,
228 which contains value `B` for `PATH`.
229 <9> Read from new read-only transaction return value `B` for `PATH` since
230 read-write transaction was committed.
232 NOTE: Examples contain blocking calls on future only to illustrate
233 that action happened after other asynchronous action. The use of the blocking call
234 `ListenableFuture#get()` is discouraged for most use-cases and you should use
235 `Futures#addCallback(ListenableFuture, FutureCallback)` to listen asynchronously
239 ==== Commit failure scenarios
241 A transaction commit may fail because of following reasons:
243 Optimistic Lock Failure::
244 Another transaction finished earlier and *modified the same node in a
245 non-compatible way*. The commit (and the returned future) will fail
246 with an `OptimisticLockFailedException`.
248 It is the responsibility of the
249 caller to create a new transaction and submit the same modification again in
250 order to update data tree.
254 `OptimisticLockFailedException` usually exposes *multiple writers* to
255 the same data subtree, which may conflict on same resources.
257 In most cases, retrying may result in a probability of success.
259 There are scenarios, albeit unusual, where any number of retries will
260 not succeed. Therefore it is strongly recommended to limit the number of
261 retries (2 or 3) to avoid an endless loop.
265 The data change introduced by this transaction *did not pass validation* by
266 commit handlers or data was incorrectly structured. The returned future will
267 fail with a `DataValidationFailedException`. User *should not retry* to
268 create new transaction with same data, since it probably will fail again.
270 ====== Example conflict of two transactions
272 This example illustrates two concurrent transactions, which derived from
273 same initial state of data tree and proposes conflicting modifications.
277 WriteTransaction txA = broker.newWriteTransaction();
278 WriteTransaction txB = broker.newWriteTransaction();
280 txA.put(CONFIGURATION, PATH, A); // <1>
281 txB.put(CONFIGURATION, PATH, B); // <2>
283 CheckedFuture<?,?> futureA = txA.submit(); // <3>
284 CheckedFuture<?,?> futureB = txB.submit(); // <4>
287 <1> Updates `PATH` to value `A` using `txA`
288 <2> Updates `PATH` to value `B` using `txB`
289 <3> Seals & submits `txA`. The commit will be processed asynchronously and
290 data tree will be updated to contain value `A` for `PATH`.
291 The returned `ListenableFuture' will complete successfully once
292 state is applied to data tree.
293 <4> Seals & submits `txB`. Commit of `txB` will fail, because previous transaction
294 also modified path in a concurrent way. The state introduced by `txB` will
295 not be applied. The returned `ListenableFuture` will fail
296 with `OptimisticLockFailedException` exception, which indicates
297 that concurrent transaction prevented the submitted transaction from being
300 ===== Example asynchronous retry-loop
304 private void doWrite( final int tries ) {
305 WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
307 MyDataObject data = ...;
308 InstanceIdentifier<MyDataObject> path = ...;
309 writeTx.put( LogicalDatastoreType.OPERATIONAL, path, data );
311 Futures.addCallback( writeTx.submit(), new FutureCallback<Void>() {
312 public void onSuccess( Void result ) {
316 public void onFailure( Throwable t ) {
317 if( t instanceof OptimisticLockFailedException && (( tries - 1 ) > 0)) {
318 doWrite( tries - 1 );
327 ==== Concurrent change compatibility
329 There are several sets of changes which could be considered incompatible
330 between two transactions which are derived from same initial state.
331 Rules for conflict detection applies recursively for each subtree
334 Following table shows state changes and failures between two concurrent
335 transactions, which are based on same initial state, `tx1` is submitted before
338 // FIXME: Providing model and concrete data structures will be probably better.
340 INFO: Following tables stores numeric values and shows data using `toString()`
341 to simplify examples.
343 .Concurrent change resolution for leaves and leaf-list items
345 |===========================================================
346 |Initial state | tx1 | tx2 | Observable Result
347 |Empty |`put(A,1)` |`put(A,2)` |`tx2` will fail, value of `A` is `1`
348 |Empty |`put(A,1)` |`merge(A,2)` |value of `A` is `2`
349 |Empty |`merge(A,1)` |`put(A,2)` |`tx2` will fail, value of `A` is `1`
350 |Empty |`merge(A,1)` |`merge(A,2)` |`A` is `2`
351 |A=0 |`put(A,1)` |`put(A,2)` |`tx2` will fail, `A` is `1`
352 |A=0 |`put(A,1)` |`merge(A,2)` |`A` is `2`
353 |A=0 |`merge(A,1)` |`put(A,2)` |`tx2` will fail, value of `A` is `1`
354 |A=0 |`merge(A,1)` |`merge(A,2)` |`A` is `2`
355 |A=0 |`delete(A)` |`put(A,2)` |`tx2` will fail, `A` does not exists
356 |A=0 |`delete(A)` |`merge(A,2)` |`A` is `2`
357 |===========================================================
359 .Concurrent change resolution for containers, lists, list items
361 |=======================================================================
362 |Initial state |`tx1` |`tx2` |Result
363 |Empty |put(TOP,[]) |put(TOP,[]) |`tx2` will fail, state is TOP=[]
365 |Empty |put(TOP,[]) |merge(TOP,[]) |TOP=[]
367 |Empty |put(TOP,[FOO=1]) |put(TOP,[BAR=1]) |`tx2` will fail, state is
370 |Empty |put(TOP,[FOO=1]) |merge(TOP,[BAR=1]) |TOP=[FOO=1,BAR=1]
372 |Empty |merge(TOP,[FOO=1]) |put(TOP,[BAR=1]) |`tx2` will fail, state is
375 |Empty |merge(TOP,[FOO=1]) |merge(TOP,[BAR=1]) |TOP=[FOO=1,BAR=1]
377 |TOP=[] |put(TOP,[FOO=1]) |put(TOP,[BAR=1]) |`tx2` will fail, state is
380 |TOP=[] |put(TOP,[FOO=1]) |merge(TOP,[BAR=1]) |state is
383 |TOP=[] |merge(TOP,[FOO=1]) |put(TOP,[BAR=1]) |`tx2` will fail, state is
386 |TOP=[] |merge(TOP,[FOO=1]) |merge(TOP,[BAR=1]) |state is
389 |TOP=[] |delete(TOP) |put(TOP,[BAR=1]) |`tx2` will fail, state is empty
392 |TOP=[] |delete(TOP) |merge(TOP,[BAR=1]) |state is TOP=[BAR=1]
394 |TOP=[] |put(TOP/FOO,1) |put(TOP/BAR,1]) |state is TOP=[FOO=1,BAR=1]
396 |TOP=[] |put(TOP/FOO,1) |merge(TOP/BAR,1) |state is TOP=[FOO=1,BAR=1]
398 |TOP=[] |merge(TOP/FOO,1) |put(TOP/BAR,1) |state is TOP=[FOO=1,BAR=1]
400 |TOP=[] |merge(TOP/FOO,1) |merge(TOP/BAR,1) |state is TOP=[FOO=1,BAR=1]
402 |TOP=[] |delete(TOP) |put(TOP/BAR,1) |`tx2` will fail, state is empty
405 |TOP=[] |delete(TOP) |merge(TOP/BAR,1] |`tx2` will fail, state is empty
408 |TOP=[FOO=1] |put(TOP/FOO,2) |put(TOP/BAR,1) |state is TOP=[FOO=2,BAR=1]
410 |TOP=[FOO=1] |put(TOP/FOO,2) |merge(TOP/BAR,1) |state is
413 |TOP=[FOO=1] |merge(TOP/FOO,2) |put(TOP/BAR,1) |state is
416 |TOP=[FOO=1] |merge(TOP/FOO,2) |merge(TOP/BAR,1) |state is
419 |TOP=[FOO=1] |delete(TOP/FOO) |put(TOP/BAR,1) |state is TOP=[BAR=1]
421 |TOP=[FOO=1] |delete(TOP/FOO) |merge(TOP/BAR,1] |state is TOP=[BAR=1]
422 |=======================================================================