Skip to content

Commit 80017fd

Browse files
sachindoddaguniDamans227
authored andcommitted
Make worker and API thread pool size configurable
The AsyncJobManager's API and Worker executor thread pool sizes are currently derived solely from db.cloud.maxActive (maxActive/2 and maxActive*2/3 respectively). This coupling doesn't account for environments with predominantly I/O-bound workers, where threads are held waiting on external responses for extended periods and higher pool sizes may be needed independently of the DB connection count. This adds two global config keys: - api.job.pool.size (default: 50) - work.job.pool.size (default: 50) The actual pool size is Math.max(configured value, db-derived default), preserving existing behavior by default while allowing operators to increase pool sizes when workloads require it. Pool sizes and their derivation are logged at startup for observability.
1 parent a951ac6 commit 80017fd

2 files changed

Lines changed: 62 additions & 5 deletions

File tree

framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
125125
Integer.class, "vm.job.lock.timeout", "1800",
126126
"Time in seconds to wait in acquiring lock to submit a vm worker job", false);
127127
private static final ConfigKey<Boolean> HidePassword = new ConfigKey<Boolean>("Advanced", Boolean.class, "log.hide.password", "true", "If set to true, the password is hidden", true, ConfigKey.Scope.Global);
128+
public static final ConfigKey<Integer> ApiJobPoolSize = new ConfigKey<>("Advanced", Integer.class, "api.job.pool.size", "50",
129+
"Minimum size of the API job executor thread pool. Actual size is the max of this value and db.cloud.maxActive / 2.", false, ConfigKey.Scope.Global);
130+
public static final ConfigKey<Integer> WorkJobPoolSize = new ConfigKey<>("Advanced", Integer.class, "work.job.pool.size", "50",
131+
"Minimum size of the Worker job executor thread pool. Actual size is the max of this value and db.cloud.maxActive * 2 / 3.", false, ConfigKey.Scope.Global);
128132

129133

130134
private static final int ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION = 3; // 3 seconds
@@ -198,7 +202,7 @@ public String getConfigComponentName() {
198202

199203
@Override
200204
public ConfigKey<?>[] getConfigKeys() {
201-
return new ConfigKey<?>[] {JobExpireMinutes, JobCancelThresholdMinutes, VmJobLockTimeout, HidePassword};
205+
return new ConfigKey<?>[] {JobExpireMinutes, JobCancelThresholdMinutes, VmJobLockTimeout, HidePassword, ApiJobPoolSize, WorkJobPoolSize};
202206
}
203207

204208
@Override
@@ -1100,13 +1104,18 @@ public boolean configure(String name, Map<String, Object> params) throws Configu
11001104
final Properties dbProps = DbProperties.getDbProperties();
11011105
final int cloudMaxActive = Integer.parseInt(dbProps.getProperty("db.cloud.maxActive"));
11021106

1103-
int apiPoolSize = cloudMaxActive / 2;
1104-
int workPoolSize = (cloudMaxActive * 2) / 3;
1107+
int defaultApiPoolSize = cloudMaxActive / 2;
1108+
int defaultWorkPoolSize = (cloudMaxActive * 2) / 3;
11051109

1106-
logger.info("Start AsyncJobManager API executor thread pool in size " + apiPoolSize);
1110+
int apiPoolSize = Math.max(ApiJobPoolSize.value(), defaultApiPoolSize);
1111+
int workPoolSize = Math.max(WorkJobPoolSize.value(), defaultWorkPoolSize);
1112+
1113+
logger.info("Start AsyncJobManager API executor thread pool in size " + apiPoolSize +
1114+
" (configured=" + ApiJobPoolSize.value() + ", db.derived=" + defaultApiPoolSize + ", db.cloud.maxActive=" + cloudMaxActive + ")");
11071115
_apiJobExecutor = Executors.newFixedThreadPool(apiPoolSize, new NamedThreadFactory(AsyncJobManager.API_JOB_POOL_THREAD_PREFIX));
11081116

1109-
logger.info("Start AsyncJobManager Work executor thread pool in size " + workPoolSize);
1117+
logger.info("Start AsyncJobManager Work executor thread pool in size " + workPoolSize +
1118+
" (configured=" + WorkJobPoolSize.value() + ", db.derived=" + defaultWorkPoolSize + ", db.cloud.maxActive=" + cloudMaxActive + ")");
11101119
_workerJobExecutor = Executors.newFixedThreadPool(workPoolSize, new NamedThreadFactory(AsyncJobManager.WORK_JOB_POOL_THREAD_PREFIX));
11111120
} catch (final Exception e) {
11121121
throw new ConfigurationException("Unable to load db.properties to configure AsyncJobManagerImpl");

framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImplTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package org.apache.cloudstack.framework.jobs.impl;
1919

20+
import java.lang.reflect.Field;
21+
2022
import com.cloud.network.Network;
2123
import com.cloud.network.dao.NetworkDao;
2224
import com.cloud.network.dao.NetworkVO;
@@ -26,10 +28,13 @@
2628
import com.cloud.vm.VirtualMachine;
2729
import com.cloud.vm.VirtualMachineManager;
2830
import com.cloud.vm.dao.VMInstanceDao;
31+
2932
import org.apache.cloudstack.api.ApiCommandResourceType;
3033
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
3134
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
3235
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
36+
import org.apache.cloudstack.framework.config.ConfigKey;
37+
import org.junit.Assert;
3338
import org.junit.Test;
3439
import org.junit.runner.RunWith;
3540
import org.mockito.InjectMocks;
@@ -93,4 +98,47 @@ public void testCleanupNetworkResource() throws NoTransitionException {
9398
Mockito.verify(networkOrchestrationService, Mockito.times(1)).stateTransitTo(networkVO,
9499
Network.Event.OperationFailed);
95100
}
101+
102+
@Test
103+
public void testPoolSizeUsesConfiguredValueWhenLarger() {
104+
int configuredSize = 3000;
105+
int dbDerivedSize = 2000;
106+
int result = Math.max(configuredSize, dbDerivedSize);
107+
Assert.assertEquals(3000, result);
108+
}
109+
110+
@Test
111+
public void testPoolSizeUsesDbDerivedValueWhenLarger() {
112+
int configuredSize = 50;
113+
int dbDerivedSize = 2000;
114+
int result = Math.max(configuredSize, dbDerivedSize);
115+
Assert.assertEquals(2000, result);
116+
}
117+
118+
@Test
119+
public void testPoolSizeDefaultConfigKeyValues() {
120+
overrideConfigValue(AsyncJobManagerImpl.ApiJobPoolSize, 50);
121+
overrideConfigValue(AsyncJobManagerImpl.WorkJobPoolSize, 50);
122+
Assert.assertEquals(Integer.valueOf(50), AsyncJobManagerImpl.ApiJobPoolSize.value());
123+
Assert.assertEquals(Integer.valueOf(50), AsyncJobManagerImpl.WorkJobPoolSize.value());
124+
}
125+
126+
@Test
127+
public void testPoolSizeConfiguredOverridesDbDerived() {
128+
overrideConfigValue(AsyncJobManagerImpl.ApiJobPoolSize, 5000);
129+
int dbMaxActive = 4000;
130+
int dbDerived = dbMaxActive / 2;
131+
int actual = Math.max(AsyncJobManagerImpl.ApiJobPoolSize.value(), dbDerived);
132+
Assert.assertEquals(5000, actual);
133+
}
134+
135+
private void overrideConfigValue(final ConfigKey configKey, final Object value) {
136+
try {
137+
Field f = ConfigKey.class.getDeclaredField("_value");
138+
f.setAccessible(true);
139+
f.set(configKey, value);
140+
} catch (IllegalAccessException | NoSuchFieldException e) {
141+
Assert.fail(e.getMessage());
142+
}
143+
}
96144
}

0 commit comments

Comments
 (0)