Reputation: 415
Background
Alfresco uses the default database transaction isolation, which for our Oracle database is READ_COMMITED. I'm working on a project where non-repeatable and phantom reads could be a major issue, so I'm looking into using SERIALIZABLE transaction isolation to avoid this.
On one hand, we'll have an custom API service which groups changes into atomic transactions - basically CRUD operations on documents. On the other hand, we'll have background processes updating the metadata of this documents running in parallel.
These operations will use Transactional Metadata Queries, as to not further complicate matters by adding eventually consistent SOLR queries into the mix.
The goal is to be able to undertake a major metadata model migration while the API service is running. For the purpose of this question, I'm going to use one attribute as the example, but IRL there will be many changes of this sort. For example, we currently have a metadata field with a constraint: mymodel:doctypes1. But we need to remap the values in mymodel:doctypes1 to a new field with a different constraint: mymodel:doctypes2. (Don't ask me why, I have no control over this decision and I personally question the wisdom of this kind of change).
My understanding of READ_COMMITTED isolation tells me that in this scenario, we are very vulnerable to the following situation:
The two values are now inconsistent: I believe this error is called a non-repeatable read.
The questions
Would setting the Oracle database to SERIALIZABLE prevent this issue? Alfresco is using Spring transactions under the hood. My understanding is that a serializable tx isolation with Spring transactions would prevent this issue from happening "transparently".
Does anyone have any real world experience setting an Alfresco database to SERIALIZABLE? Were you trying to solve a similar issue? Did it work? What kind of performance impact did it have for you?
Thanks you very much for sharing your experiences!
Upvotes: 0
Views: 1112
Reputation: 415
Axel Faust over at the Alfresco forum helped me out with this by pointing out that the RetryingTransactionHelper enforces optimistic locking and retries if two transactions overlap. If you're curious about this, I would recommend his post.
Just to make sure I did a little mockup to provoke a ConcurrencyException and check that it does indeed behave correctly.
I use a master class to send 49 updates for the same node to the thread pool:
for (int i=0; i < 50; i++) {
TestIncrementer ti = new TestIncrementer(node);
threadPoolExecuter.submit(ti);
}
My incrementer just reads the existing title, sleeps randomly, and then adds onto it.
int hash = RetryingTransactionHelper.getActiveUserTransaction().hashCode();
log.debug("Thread id: " + Thread.currentThread().getId() + " tx " + hash);
String title = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE);
Thread.sleep(Math.round(Math.random()* 1000));
nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, title + "1");
log.debug("Title: " + title);
As expected, I'm seeing concurrency exceptions and retries:
Transaction commit failed:
Thread: defaultAsyncAction4
Txn: UserTransaction[object=org.alfresco.util.transaction.SpringAwareUserTransaction@65b249f2, status=0]
Iteration: 8
Exception follows:
org.springframework.dao.ConcurrencyFailureException: Failed to update node 126094
But they are getting retried.
The final retry leaves the node with exactly 49 aggregations! Which is exactly what needs to happen.
The conclusion is that changing the database isolation is definitely not necessary or nor even desirable if you use the RetryingTransactionHelper for your background processes.
Upvotes: 2