Table of Contents

Transactions

Why we need transactions

Transactions in an ODBMS environment fulfill exactly the same functions that they do in most other transactional environments, they provide boundaries that delineate the ACID properties associated with changes to the databases. ACID is an acronym for Atomic, Consistent, Isolated and Durable. Transactions provide a simple model that defines the success or failure of a particular operation or change made to the repository. A transaction either commits (in other words it succeeds and all of the changes that were made under it are effected), or it aborts (none of the changes made during the transaction are effected). This all-or-nothing quality makes for a simple programming model.

The attributes of an ACID transaction are:

Atomicity

A transaction allows for the grouping of one or more changes to objects in the repository to form an atomic or indivisible operation. That is, either all of the changes occur or none of them do. If for any reason the transaction cannot be completed, everything this transaction changed can be restored to the state it was in prior to the start of the transaction via an abort operation.

Consistency

Transactions always operate on a consistent view of the repository and when they end always leave the repository in a consistent state. When changes are made to the repository, inconsistent changes that are part of the operation are hidden from the repository, until the end of the transaction. Once the transaction either succeeds (i.e. commits) or fails (i.e. aborts) the repository is left in a state that does not violate the internal consistency of the repository.

Isolation

To a given transaction, it should appear as though it is running all by itself on the repository. The effects of concurrently running transactions are invisible to this transaction, and the effects of this transaction are invisible to others until the transaction is committed.

Durability

Once a transaction is committed, its effects are guaranteed to persist even in the event of subsequent system failures. Until the transaction commits, not only are any changes made by that transaction not durable, but are guaranteed not to persist in the face of a system failure, as crash recovery will rollback their effects.

The simplicity of ACID transactions is especially important in a distributed database environment such as Facets where the transactions are potentially being made simultaneously from multiple JVMs potentially on physically disparate machines.

Why ACID simplifies the programming model

It was mentioned in the first paragraph that the ACID properties of the Facets transactional model simplifies programming and this is indeed so, because the fact that all the operations performed inside a transaction either all succeed or all fail not only makes the amount of code we have to write less, but also makes the nature of that code less complex. This simplification is achieved because we do not have to create multiple if then else type statements to manage the consistency of the repository.

Consider the following example: we have to update the state of several objects that represent the flow of work through a particular company. Let us say we have a document object that needs to move from one workflow state to another, as well as two user objects that represent the previous owner of the document and the next owner of the document. The way we handle this using the ACID model is as follows:


	begin transaction
	   change state of document from state x to state y
	   change owner of document from previous to next
	   remove document from previous user documents
	   add document to next user documents
	commit transaction
	handle potential failure

If we had to do this without the "succeed all/fail all" model, we would have to write code something like this :


	change state of document from state x to state y
	if change succeeded then
	   change owner of document from previous to next
		  if change succeeded then
			 remove document from previous user documents
			 if change succeeded then
				add document to next user documents
				if change failed then
				   add document back to previous user documents
				   change owner of document to previous from next
				   change state of document back to x from y
				else
				   change owner of document to previous from next
				   change state of document back to x from y
				endif
			 else
		  change document state back to x from y
	   endif
	endif

As you can see from the small example above, the code without the ACID programming model is more complex, and as the number of changes grows, the complexity grows almost exponentially. Another consequence of the non-ACID approach that is not immediately apparent in the example above, is that changes that have been made to the state of objects by other threads could potentially interfere with the execution of the above example. This is one of the important effects of the Isolation attribute.

Transactions and Commit Records

Facets is in inherently multi-user i.e. multi-threaded and multi-VM environment which means that while your transaction is open (i.e. between a begin and an end) Facets maintains a consistent view of the repository for all the other threads and VMs that are reading or writing persistent objects at that instant in time. Facets achieves this by creating "views" of the repository for these threads. While your transaction is open, Facets also has to manage the transactions that other threads or VMs are committing, and it achieves this by creating Commit Records.

Commit Records hold the required state changes for all the objects that were affected by a transaction. Until your code commits, Facets can not update the repository and must balance the requirements of your code and all the other pieces of code that are running simultaneously. If your thread opens a transaction and takes a long time before committing, the number of Commit Records needing to be processed can build up. This build up, known as a Commit Record Backlog, can have very adverse effects on the performance of the Facets environment as a whole, since more and more system resources are required to manage the concurrent and different views of the repository required by the executing threads. It is therefore advisable to begin your transaction and commit or abort your changes quickly and efficiently in order to ensure optimal database performance.

See also : Commit Records and Transaction Logs

Starting a Transaction

Starting a transaction in Facets is very simple, just ask the current Session object to begin a transaction as follows:


	GsSession aSession;
	
	// obtain session from session factory
	try {
	   aSession.begin();
	} catch (GsTransactionInProgressException exception) {
	   // handle exception
	   exception.printStackTrace();
	}

We have to handle the exceptional case that a transaction is already underway on that session object, this could potentially occur when two threads (erroneously) make use of the same session object. Once we have started a session we can start making persistent changes to the objects in the repository or adding objects to the repository that we wish to make persistent. All changes made to objects that are currently in the repository or that can be reached by objects that are in the repository will be noted and will be made permanent once the transaction is committed (if it succeeds of course).

Committing a Transaction

Requesting that the current transaction be committed is just as simple as starting one, the commit method is invoked on a GsSession object. In the case of a commit more than one type of exception may be raised since there are a number of things that can go wrong. It is advisable to always handle each of the exceptions that can occur when a commit takes place, and always remember to abort the transaction if it fails, to ensure that the Commit Record is released as soon as possible. A complete example is shown below


	//
	// small example to show a complete transaction
	// we refer to the previous example for workflow
	//
	public class WorkflowDocument {
	   public boolean changeStateTo(State newState) {
		  GsSession        session;
		  SessionFactory   factory;
	
		  factory = SessionFactory.getInstance();
	      try {
			 session = factory.getSession();
		  } catch (Exception exception) {
	
			 // exception handling code
			 exception.printStackTrace();
	         return(false);
		  }
	      try {
			 session.begin();
			 currentUser().currentDocuments().removeDocument(this);
			 setCurrentUser(nextUserForState(newState));
			 setState(newState);
			 currentUser().currentDocuments().addDocument(this);
			 session.commit();
	         return(true);
		  } catch (GsTransactionInProgressException exception1) {
	         // handle the case where no session available
			 exception1.printStackTrace();
		  } catch (Exception exception) {
	         // handle the case where we have to abort
			 session.abort();
			 exception.printStackTrace();
		  } finally  {
			 session.close();
		  }
	      return(false);
	   }
	}

Best Practices for Sessions and Transactions

Best Practice: Recycle Sessions Quickly

As mentioned previously, it is important to remember that Sessions are precious system resources and therefore they should be managed carefully. Always release a Session back to the system as soon as possible to allow Facets to re-use the resources. In practical terms this means obtaining a session from the SessionFactory just before you need it and sending it the close() message as soon as you have finished using it. Typically the release of the session should be managed by a finally clause, to ensure that even in the event of an error it is returned to the system.

Best Practice: Make Transactions Short

The Commit Record issues discussed previously can be prevented by following some simple rules when writing code that uses transactions. Always ensure that long operations take place outside of a transaction. This is often quite simple, however sometimes, complex time consuming code has to be executed in a transactional manner because it has persistent side effects. If this is the case, try to factor your code into smaller pieces and wrap each of these pieces in a transaction. Doing this will ensure that Facets can integrate your changes into the repository without a Commit Record Backlog. For example, never perform operations that involve interaction with a user inside of a transaction. The amount of time a user takes to respond is an eternity for a busy Facets system and in some cases the user may never respond, which could have major negative consequences on the Facets environment. If you are interacting with an external system that might fail to respond, rather attempt to interact with that system outside of a transaction.

Best Practice: Abort Soon

It is important to remember to abort a transaction as soon as possible after it has failed, since only aborting will clear the Commit Record Backlog. In some cases you may need to examine the objects that cause the conflict. Be careful when doing this because the conflict set may be very large and may take a considerable amount of time to be fetched from the repository. If this is done prior to an abort, this could also lead to a Commit Record Backlog.

©2005 GemStone Systems, Inc.