Posts Tagged ‘performance

20
Dec
09

Hibernate SequenceGenerator with allocationSize=1 leads to huge contention

This post is a kind of warn for Hibernate users.

I sincerely don’t know which was the reason but accidentally someone annotated every entity on a software with:

@SequenceGenerator(allocationSize=1)

Doing a brainstorm to find the reason we can speculate:

  1. Minimise sequence gaps due to server reboots (unlikely)
  2. Misunderstanding of what the parameter really does. Probably the developer thought it had to do with the increment by parameter of the underlying database sequence (more likely)

Whichever the reason the truth is that whenever you annotate an entity with @SequenceGenerator Hibernate (at least I know up to 3.3.1.GA) delegates sequence generation to org.hibernate.id.SequenceHiLoGenerator which in turn generate method is as follows:

public synchronized Serializable generate(SessionImplementor session, Object obj)
	throws HibernateException {
		if (maxLo < 1) {
			//keep the behavior consistent even for boundary usages
			long val = ( (Number) super.generate(session, obj) ).longValue();
			if (val == 0) val = ( (Number) super.generate(session, obj) ).longValue();
			return IdentifierGeneratorFactory.createNumber( val, returnClass );
		}
		if ( lo>maxLo ) {
			long hival = ( (Number) super.generate(session, obj) ).longValue();
			lo = (hival == 0) ? 1 : 0;
			hi = hival * ( maxLo+1 );
			if ( log.isDebugEnabled() )
				log.debug("new hi value: " + hival);
		}

		return IdentifierGeneratorFactory.createNumber( hi + lo++, returnClass );
	}

If you compare the generate sequence above with the one from org.hibernate.id.SequenceGenerator below you’ll notice that this one in turn is not synchronized:

public Serializable generate(SessionImplementor session, Object obj)
	throws HibernateException {

		try {

			PreparedStatement st = session.getBatcher().prepareSelectStatement(sql);
			try {
				ResultSet rs = st.executeQuery();
				try {
					rs.next();
					Serializable result = IdentifierGeneratorFactory.get(
							rs, identifierType
						);
					if ( log.isDebugEnabled() ) {
						log.debug("Sequence identifier generated: " + result);
					}
					return result;
				}
				finally {
					rs.close();
				}
			}
			finally {
				session.getBatcher().closeStatement(st);
			}

		}
		catch (SQLException sqle) {
			throw JDBCExceptionHelper.convert(
					session.getFactory().getSQLExceptionConverter(),
					sqle,
					"could not get next sequence value",
					sql
				);
		}

	}

The synchronized keyword on the previous one isn’t any surprise since SequenceHiLoGenerator does some of its sequence generation in memory and it may be accessed concurrently by multiple threads.

But the fact is that SequenceHiLoGenerator wasn’t designed to have an allocationSize=1 (strangely enough it has a separate flow for this situation).

Reproducing this scenario seems to be very easy, sincerely I have not tried to do a syntetic reproduction but I guess that spawning a few threads with a simple entity configured with allocationSize=1 and having all the threads call a Session.persist might do the trick. All you will need to do is have a thread dump after a few minutes (3 or 5) of execution and import it into a dump analyser (IBM Support Assistant if you are running on an IBM JVM) and you’ll notice that a great deal of threads will be waiting on a lock (before entering the generate method on AbstractSaveEventListener.saveWithGeneratedId).

But what would be possible alternatives for this?

If you want to have the entities IDs reflecting the value of the database sequence use the following annotation:


@GenericGenerator(strategy="sequence")

Another possibility is removing the allocationSize (which leads to the default value of 50) or configuring it with a greater value. But, in this case you’ll need to update the database sequence so that you don’t end up with a gap on your IDs since SequenceHiLoGenerator employs the following formula for sequence generation:

DBSequence*allocationSize<= IDs < (DBSequence+1)*allocationSize

And upon startup it already increments sequence by one acquiring a new slot of IDs.

So you’ll have to restart your sequence with the following value:


select round(max(id)/allocationSize) from table;

03
Mar
09

Architecture Evaluation using ATAM

Those who work as a Software Architect have already been tasked with something like:

“We need to develop an Web application with a huge aptitude to scale horizontally and also able to conform with tight response times”

If you are really an architect, you’ll start to make up the architecture either in your mind, on an UML tool or even on a simple paper. It is as easy as it was coded on you, by heart you start to scratch how the solution would look like and in the end it’ll probably meet all the functional and non-functional requirements you had been told.
This works in this scenario but imagine if now, instead of coming up with a proposal of an architecture you are tasked to evaluate someone else architecture.
This is the scenario where ATAM is the way to go. It was designed as a sucessor of Software Architecture Analysis Method (SAAM). ATAM is also already a very accepted methodology.

ATAM authors define it as:

“The purpose of the ATAM is to assess the on sequences of architectural decisions in light of quality attribute requirements.”

ATAM is based on meetings with key stakeholders from diferent areas. During these meetings key quality attributes (QA) are formalized for the architecture and are later detailed, they are also further prioritized in relation to the importance of each QA and also the risk of not having this QA complete.

The last step is the generation of a report containing (mainly):

  • Risks and Non-risks
  • Sensitivity and Tradeoff Points

If you have alredy this need, give a look on ATAM. I’ll try to post some insights on how to apply it on the next few weeks.




ClustrMaps

Blog Stats

  • 384,631 hits since aug'08