AS bootime improvement - On demand deployment of EJB3 timerservice
jaikiran Aug 26, 2010 5:10 AMSince JBoss AS 6.0.0.M4 JBoss EJB3 has moved to a new timerservice implementation for EJB3.1 which is now based on JPA. Ever since this was integrated into AS, people who keep a watch on the AS boot time would have noticed that the AS startup time is increased by around 3-4 seconds. These 3-4 seconds are spent on deploying the persistence unit contained in the timerservice implementation. This new timerservice implementation is only relevant for EJB3.x deployments, which means that we really don't have to deploy it until there's a first EJB3.x deployment in the server. By default, AS doesn't ship any EJB3.x (or other EJB) apps. This means that we can save these 3-4 seconds by deploying the timerservice on demand. So with that in mind, I decided to come up with a way to deploy timerservice only when there's a first EJB3.x deployment. Inspired by Brian's on-demand web application deployment support since 6.0.0.M2 (http://community.jboss.org/thread/147220 and http://community.jboss.org/wiki/On-DemandDeploymentofWebApplications), I now have a org.jboss.ejb3.deployers.MetaDataBasedOnDemanDeploymentDeployer (within AS trunk). This deployer uses the ProfileService to deploy an on-demand EJB3 profile. The on-demand EJB3 profile currently consists of just one single deployment JBOSS_HOME/common/deploy/jboss-ejb3-timerservice-mk2.jar (Note that in this process, I have moved the timerservice deployment jar to JBOSS_HOME/common/deploy, similar to the admin-console.war). This deployer works as follows: 1) Picks up deployments which have JBossMetaData as a attachment 2) Further checks whether the JBossMetaData corresponds to EJB3.x deployment 3) If it's a EJB3.x deployment, it activates the profile configured (in the -deployer-jboss-beans.xml). That profile current contains timerservice deployment jar The code of the deployer is very similar to the OnDemandContextProfileManager (except that this new MetaDataBasedOnDemanDeploymentDeployer doesn't depend on any web related services, for obvious reasons). Here's what it looks like: {code:java} public class MetaDataBasedOnDemanDeploymentDeployer extends AbstractDeployer { private static final Logger log = Logger.getLogger(MetaDataBasedOnDemanDeploymentDeployer.class); private static final String DEFAULT_EJB3_ONDEMAND_PROFILE_NAME = "EJB3-OnDemand-Profile"; /** The profile service */ private ProfileService profileService; /** The root of the profile */ private URI deploymentRoot; /** The deployment names. */ private Collection deploymentNames; /** Whether this node has activated its profile */ private boolean activated; /** The profile service key domain */ private String profileDomain; /** The profile service key server */ private String profileServer; /** The profile service key name */ private String profileName; /** The profile service key */ private ProfileKey profileKey; private boolean activateOnDemand = true; public MetaDataBasedOnDemanDeploymentDeployer() { this.setStage(DeploymentStages.POST_CLASSLOADER); this.setInput(JBossMetaData.class); // ordering this.addInput(AttachmentNames.PROCESSED_METADATA); } /** * Get the deployment root * * @return the deployment root */ public URI getDeploymentRoot() { return deploymentRoot; } /** * Set the deployment root. * * @param deploymentRoot the deployment root */ public void setDeploymentRoot(URI deploymentRoot) { this.deploymentRoot = deploymentRoot; } /** * Get the deployment names. * * @return the deployment names */ public Collection getDeploymentNames() { return deploymentNames; } /** * Set the deployment names * * @param deploymentNames the deployment names */ public void setDeploymentNames(Collection deploymentNames) { this.deploymentNames = deploymentNames; } /** * Set a single deployment * * @param name the deployment name */ public void setSingleDeployment(String name) { this.deploymentNames = Collections.singleton(name); } /** * Gets the value that should be used for the * {@link ProfileKey#getDomain() domain} portion of * the on-demand @{link Profile}'s {@link #getProfileKey() ProfileKey}. * * @return the domain, or
null
if not set */ public String getProfileDomain() { return profileDomain; } /** * Sets the value that should be used for the * {@link ProfileKey#getDomain() domain} portion of * the singleton @{link Profile}'s {@link #getProfileKey() ProfileKey}. * * @param profileDomain the domain, ornull
*/ public void setProfileDomain(String profileDomain) { this.profileDomain = profileDomain; } /** * Gets the value that should be used for the * {@link ProfileKey#getServer() server} portion of * the on-demand @{link Profile}'s {@link #getProfileKey() ProfileKey}. * * @return the server, ornull
if not set */ public String getProfileServer() { return profileServer; } /** * Sets the value that should be used for the * {@link ProfileKey#getServer() server} portion of * the on-demand @{link Profile}'s {@link #getProfileKey() ProfileKey}. * * @param profileServer the server, ornull
*/ public void setProfileServer(String profileServer) { this.profileServer = profileServer; } /** * Gets the value that should be used for the * {@link ProfileKey#getName() name} portion of * the on-demand @{link Profile}'s {@link #getProfileKey() ProfileKey}. * * @return the name, ornull
if not set and * {@link #setContextName(String) contextName} is also unset. */ public String getProfileName() { if (profileName == null) { this.profileName = DEFAULT_EJB3_ONDEMAND_PROFILE_NAME; } return profileName; } /** * Sets the value that should be used for the * {@link ProfileKey#getName() name} portion of * the singleton @{link Profile}'s {@link #getProfileKey() ProfileKey}. * * @param profileName the name, ornull
*/ public void setProfileName(String profileName) { this.profileName = profileName; } /** * Gets whether this object has activated its profile. * * @returntrue
if {@link #activateProfile()} has successfully * completed and {@link #releaseProfile()} has not been called; *false
otherwise. */ public boolean isActivated() { return activated; } /** * Sets the ProfileService reference. * * @param profileService the profileService. Cannot benull
* * @throws IllegalArgumentException ifprofileService
isnull
*/ public void setProfileService(ProfileService profileService) { if (profileService == null) { throw new IllegalArgumentException("profileService is null"); } this.profileService = profileService; } /** * Gets whether the profile should be activated on during the {@link #start()} * phase of this bean's deployment rather than on receipt of an HTTP request. * This property allows a simple configuration to turn off the "on-demand" * behavior for environments (e.g. production servers) where a more * deterministic startup is appropriate. * * @returnfalse
if the profile should be activated as part of * startup of this bean;true
if activation should * be deferred until an HTTP request is received. Default is *true
* * @deprecated This is a temporary API for AS 6.0.0.M2; something else * may replace it in later releases */ public boolean isActivateOnDemand() { return activateOnDemand; } /** * Sets whether the profile should be activated on during the {@link #start()} * phase of this bean's deployment rather than on receipt of an HTTP request. * This property allows a simple configuration to turn off the "on-demand" * behavior for environments (e.g. production servers) where a more * deterministic startup is appropriate. * * @param activateOnDemandfalse
if the profile should be * activated as part of startup of this bean; *true
if activation should be * deferred until an HTTP request is received. * * @deprecated This is a temporary API for AS 6.0.0.M2; something else * may replace it in later releases */ public void setActivateOnDemand(boolean activateOnDemand) { this.activateOnDemand = activateOnDemand; } /** * Builds a profile from the {@link #getURIList() URI list} and registers * it under the configured {@link #getProfileKey()}. */ public void start() throws Exception { if (profileService == null) { throw new IllegalStateException("Must configure ProfileService"); } if (deploymentRoot == null) { throw new IllegalStateException("Must configure deployment root"); } if (deploymentNames == null) { throw new IllegalStateException("Must configure deployment name(s)"); } // TODO add dependencies on bootstrap profiles String[] rootSubProfiles = new String[0]; // Create a hotdeployment profile // FIXME JBAS-7720 restore hot deploy capability (and make it configurable too) ProfileMetaData profileMetaData = ProfileMetaDataFactory.createFilteredProfileMetaData(getProfileName(), deploymentRoot, this.deploymentNames.toArray(new String[this.deploymentNames.size()])); this.profileKey = this.profileService.registerProfile(profileMetaData); if (this.activateOnDemand == false) { // FIXME we don't validate as we expect the PS to do it at the end // of startup; need to check if this is correct activateProfile(false); } } /** * Unregisters the profile registered in {@link #start()}. */ public void stop() throws Exception { ProfileKey profKey = null; try { profKey = getProfileKey(); } catch (IllegalStateException e) { return; } if (profileService != null && profKey != null) { try { // Inactivate first if needed if (profileService.getActiveProfileKeys().contains(profKey)) { releaseProfile(); } profileService.unregisterProfile(profKey); } catch (NoSuchProfileException e) { log.warn("Could not unregister unknown profile " + profKey); } finally { } } } /** * Tells the ProfileService to * {@link ProfileService#activateProfile(ProfileKey) activate the on-demand profile}. */ public void activateProfile() throws Exception { activateProfile(true); } /** * Gets the key for the {@link Profile} that we activate and release. * * @return the key. Will not returnnull
* * @throws IllegalStateException if {@link #getProfileName()} returnsnull
* * @see #getProfileDomain() * @see #getProfileServer() * @see #getProfileName() */ public ProfileKey getProfileKey() { if (this.profileKey == null) { String profileName = getProfileName(); if (profileName == null) { throw new IllegalStateException("Must configure profileName or contextName before calling getProfileKey()"); } this.profileKey = new ProfileKey(getProfileDomain(), getProfileServer(), profileName); } return this.profileKey; } @Override public void deploy(DeploymentUnit unit) throws DeploymentException { if (this.isActivated()) { return; } JBossMetaData metadata = unit.getAttachment(JBossMetaData.class); if (metadata.isEJB3x() == false) { return; } // activate the on-demand profile try { this.activateProfile(true); } catch (Exception e) { throw new DeploymentException("Could not activate on-demand profile: " + this.getProfileName() + " while deploying unit: " + unit); } } private synchronized void activateProfile(boolean validate) throws Exception { if (this.profileService == null) { throw new IllegalStateException("Must configure the ProfileService"); } ProfileKey profKey = getProfileKey(); if (this.profileService.getActiveProfileKeys().contains(profKey) == false) { this.profileService.activateProfile(profKey); if (validate) { // Validate if the activation was successful this.profileService.validateProfile(profKey); } this.activated = true; } else { log.warn("Profile " + profKey + " is already activated"); this.activated = true; } } /** * Tells the ProfileService to {@link ProfileService#releaseProfile(ProfileKey) release the profile}. * Called by the HASingletonController when we are no longer the singleton master. */ private synchronized void releaseProfile() throws Exception { if (this.activated) { try { this.profileService.deactivateProfile(getProfileKey()); } catch (NoSuchProfileException e) { log.warn("No Profile is registered under key " + getProfileKey()); } this.activated = false; } } } {code} The deployer is configured as follows: {code:xml} <bean name="MetadataBasedOnDemandDeploymentDeployer" class="org.jboss.ejb3.deployers.MetaDataBasedOnDemanDeploymentDeployer"> <property name="profileService"><inject bean="jboss.profileservice:service=ProfileService"/></property> <!-- Build a profile from the contents of this single URI --> <property name="deploymentRoot">${jboss.common.base.url}deploy</property> <property name="singleDeployment">jboss-ejb3-timerservice-mk2.jar</property> <!-- Whether activation of the profile (i.e. deployment of the URI contents) should be deferred until the first request (true) or done as part of the start of this bean (false). WARNING: This configuration option may be altered in a subsequent AS 6.0 milestone release. FIXME: https://jira.jboss.org/jira/browse/JBAS-7714 --> <property name="activateOnDemand">${jboss.as.deployment.ondemand:true}</property> </bean> {code} I just gave this a try against AS trunk and I can see that my server boots 3-4 seconds faster (as expected). I have further tested that when there's a EJB3 deployment in the server, the timerservice is deployed on demand. This deployer probably need not be EJB3 specific, but I don't have any plans right now to make it more general. Expect for some name cleanup and javadocs, the current version looks good enough. If there are no objections, I plan to introduce this in AS trunk soon. Thoughts?