Ticket #897 (closed enhancement: fixed)

Opened 9 months ago

Last modified 8 months ago

Start read-only transactions where possible

Reported by: ronald Assigned to: ronald
Priority: critical Milestone: 0.9.0
Component: ambra Version: 0.8.3-SNAPSHOT
Keywords: Cc:
Blocking: Blocked By:

Description

With read-only transaction support in OTM and Mulgara, and the fact that these can run concurrently with each other and with any write transaction, we need make sure we use read-only transactions wherever possible in the pub-app. This will result in significant performance improvement because Mulgara will be able to fully utilize all 4 processors on the box instead of only just 1 as now.

Dependency Graph

Change History

04/04/08 18:45:20 changed by amit

  • priority changed from unassigned to critical.

05/06/08 09:22:45 changed by ronald

  • status changed from new to assigned.
  • blocking changed.
  • blockedby changed.

05/14/08 07:07:41 changed by ronald

  • status changed from assigned to closed.
  • resolution set to fixed.

(In [5734]) Implement separation of read-write vs read-only transactions.

The struts interceptors presented a bit of problem. While it would be preferable to have a single tx encompass everything, there are 3 reasons why the main tx needs to be started after the interceptors, i.e. at the action layer:

  1. some operations (e.g. ingest) require multiple tx to be run as part of a single request (in order to avoid having one problematic article cause the whole batch to be rolled back, and in order to work around problems with search indexing right now that mean indexing can't be done until after the commit).
  2. there are currently 376 url paths this app handles, but "only" 85 actions; tracking read-write vs read-only on a per action(-method) is therefore less error-prone and easier to keep up-to-date.
  3. OTM currently has a non-negligible overhead to check whether any changes to any objects have occurred, i.e. to determine if anything needs to be written out to the stores on a commit (flush). This means that there's a noticeable penalty to start transactions when they are known to not be needed, especially if the session contains many objects, such as on the getters on the actions.

On the other hand, some interceptors (sometimes) need a tx, more commonly to look up user-account information. Even worse, EnsureUserAccountInterceptor? sometimes requires a write transaction (the the user's email changed) but most often doesn't, and whether or not this write tx is needed is data dependent and hence can't be predicated from the request-uri or the action targeted.

The result is that there is a main tx that gets started on the action's, and there may be one or two side tx that are run by the interceptors.

One limitation of tx injection is that it only works for methods on spring-managed beans that are invoked from outside that bean (i.e. internal method invocations don't trigger the injected tx management code). The upshot is that the interceptors either have to do manual tx management (e.g. our TransactionHelper?) or they must call into another bean for operations needing a tx; I've chosen the latter for consistency. This only really affected one place, the UserAccountsInterceptor?, where code was moved into the UserService?; in all other cases the interceptors were already calling into services.

This now left the question of how to configure the transaction-entry points, together with the read-only vs read-write flag. The two options are putting @Transactional annotations on methods or creating pointcut expressions for the methods in the spring config. The latter has the advantage of not requiring any code changes and keeping the dependency on spring smaller; the former has the advantage of putting the declarations near the code that requires the tx, making it more obvious for example whether a read-only or a read-write tx is needed.

In this commit the @Transactional route was taken. Annotations have been put on both the service methods (all of them, even though it could've been limited to just those called by the interceptors) and the actions (except in those cases where the action method's body was just a single call to the service).

This closes #897.