Skip to content

Scale hotspot in DefaultSingletonBeanRegistry#registerDependentBean #34981

@SameerKhan15

Description

@SameerKhan15

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.

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)status: duplicateA duplicate of another issue

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions