-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Description
I work for an enterprise company and we are working on a very high scale use case that involves request throughput in the range of several thousand per sec per app server. We have been observing the current locking scheme in registerDependentBean as a scale hotspot.
So, I wrote ConcurrentLinkedHashSet as Custom Collection (see git link below). And we have tested it with the below change and the hotspot gets solved.
Version#1: This is already scale tested in our workload and yields very good scale, and we have not observed any issues with this change, for at least our workload
...
private final Map<String, ConcurrentLinkedHashSet<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
private final Map<String,ConcurrentLinkedHashSet<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
...
public void registerDependentBean(String beanName, String dependentBeanName) {
...
Set<String> dependentBeans = this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new
ConcurrentLinkedHashSet<>());
if (!dependentBeans.add(dependentBeanName)) {
return;
}
...
Set<String> dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new
ConcurrentLinkedHashSet<>());
dependenciesForBean.add(canonicalName);
}
Version#2: A more "conservative" approach. We have not scale tested it yet but are in the process of doing so.
private boolean checkForBeanExistence(Map<String, ConcurrentLinkedHashSet<String>> map, String key, String val) {
Set<String> values = map.get(key);
if (values != null && values.contains(val)) {
return true;
}
return false;
}
public void registerDependentBean(String beanName, String dependentBeanName) {
String canonicalName = canonicalName(beanName);
if (!checkForBeanExistence(this.dependentBeanMap, canonicalName, dependentBeanName)) {
synchronized(this.dependentBeanMap) {
Set<String> dependentBeans = this.dependentBeanMap.computeIfAbsent(canonicalName, k
-> new ConcurrentLinkedHashSet<>());
if (!dependentBeans.add(dependentBeanName)) {
return;
}
}
}
if (!checkForBeanExistence(this.dependenciesForBeanMap, dependentBeanName, canonicalName)) {
synchronized (this.dependenciesForBeanMap) {
Set<String> dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new
ConcurrentLinkedHashSet<>());
dependenciesForBean.add(canonicalName);
}
}
}
So, I wanted to gauge the appetite for working through a proposal like the above. The biggest question is the functional correctness of a change like this. I ran these through spring tests and they all pass. And we have tested Version#1 in our scale workload and that works too. What other tests would we need to evaluate this? And if you see any problems with the above approach, we can have a discussion here.