fix: Reload 동시성 수정 - volatile ConcurrentHashMap + 원자적 맵 교체

1. 맵 5개를 volatile + ConcurrentHashMap으로 변경
   - 비동기 읽기(isGroupPass 등) 중 HashMap 구조 변경으로 발생하던 race condition 제거

2. initAdapter/Interface/Group()에서 빌드-완성 후 원자적 교체 패턴 적용
   - 신규 맵을 완전히 구성한 뒤 this.field = newMap으로 한 번에 교체
   - 구→신 전환 중 불완전 상태가 외부에 노출되던 문제 제거

3. removeAdapter/Interface/Group()에 synchronized 추가
   - 락 없이 맵을 수정하던 메서드와 reload 사이의 race condition 방지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curry772
2026-04-23 15:25:58 +09:00
parent d88dbe1eef
commit 632550220d
@@ -7,6 +7,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -39,11 +40,11 @@ public class InflowControlManager implements Lifecycle, Bucket {
public static final String QUOTA_TIMEUNIT_DAY = "DAY";
public static final String QUOTA_TIMEUNIT_MONTH = "MON";
private Map<String, CustomBucket> adapterBucketList = new HashMap<>();
private Map<String, CustomBucket> interfaceBucketList = new HashMap<>();
private Map<String, CustomGroupBucket> groupBucketList = new HashMap<>();
private Map<String, String> interfaceToGroupMap = new HashMap<>();
private Map<String, InflowGroupVO> groupVoMap = new HashMap<>();
private volatile Map<String, CustomBucket> adapterBucketList = new ConcurrentHashMap<>();
private volatile Map<String, CustomBucket> interfaceBucketList = new ConcurrentHashMap<>();
private volatile Map<String, CustomGroupBucket> groupBucketList = new ConcurrentHashMap<>();
private volatile Map<String, String> interfaceToGroupMap = new ConcurrentHashMap<>();
private volatile Map<String, InflowGroupVO> groupVoMap = new ConcurrentHashMap<>();
private boolean started;
private LifecycleSupport lifecycle = new LifecycleSupport(this);
@@ -97,37 +98,39 @@ public class InflowControlManager implements Lifecycle, Bucket {
}
private void initAdapter() throws Exception {
adapterBucketList = new HashMap<>();
Map<String, CustomBucket> newMap = new HashMap<>();
List<InflowTargetVO> inflowAdapterVoList = inflowControlDAO.getInflowAdapterList();
for (InflowTargetVO inflowVo : inflowAdapterVoList) {
if (InflowControlManager.isInflowTarget(inflowVo)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
if (bucket != null) {
adapterBucketList.put(inflowVo.getName(), bucket);
newMap.put(inflowVo.getName(), bucket);
}
}
}
this.adapterBucketList = newMap;
}
private void initInterface() throws Exception {
interfaceBucketList = new HashMap<>();
Map<String, CustomBucket> newMap = new HashMap<>();
List<InflowTargetVO> inflowInterfaceVoList = inflowControlDAO.getInflowInterfaceList();
for (InflowTargetVO inflowVo : inflowInterfaceVoList) {
if (InflowControlManager.isInflowTarget(inflowVo)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
if (bucket != null) {
interfaceBucketList.put(inflowVo.getName(), bucket);
newMap.put(inflowVo.getName(), bucket);
}
}
}
this.interfaceBucketList = newMap;
}
private void initGroup() throws Exception {
groupBucketList = new HashMap<>();
interfaceToGroupMap = new HashMap<>();
groupVoMap = new HashMap<>();
Map<String, CustomGroupBucket> newGroupBucketList = new HashMap<>();
Map<String, String> newInterfaceToGroupMap = new HashMap<>();
Map<String, InflowGroupVO> newGroupVoMap = new HashMap<>();
List<InflowGroupVO> inflowGroupVoList = inflowControlDAO.getInflowGroupList();
@@ -135,20 +138,24 @@ public class InflowControlManager implements Lifecycle, Bucket {
if (isInflowGroupTarget(groupVo)) {
CustomGroupBucket bucket = makeGroupBucket(groupVo);
if (bucket != null) {
groupBucketList.put(groupVo.getGroupId(), bucket);
groupVoMap.put(groupVo.getGroupId(), groupVo);
newGroupBucketList.put(groupVo.getGroupId(), bucket);
newGroupVoMap.put(groupVo.getGroupId(), groupVo);
// 인터페이스 → 그룹 매핑 등록
for (String interfaceId : groupVo.getInterfaceList()) {
interfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
newInterfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
}
}
}
}
if (logger.isInfo()) {
logger.info("InflowControlManager] initGroup completed. groups=" + groupBucketList.size()
+ ", mappings=" + interfaceToGroupMap.size());
logger.info("InflowControlManager] initGroup completed. groups=" + newGroupBucketList.size()
+ ", mappings=" + newInterfaceToGroupMap.size());
}
this.groupBucketList = newGroupBucketList;
this.interfaceToGroupMap = newInterfaceToGroupMap;
this.groupVoMap = newGroupVoMap;
}
public synchronized void reloadAdapter() throws Exception {
@@ -293,11 +300,11 @@ public class InflowControlManager implements Lifecycle, Bucket {
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
adapterBucketList.clear();
interfaceBucketList.clear();
groupBucketList.clear();
interfaceToGroupMap.clear();
groupVoMap.clear();
adapterBucketList = new ConcurrentHashMap<>();
interfaceBucketList = new ConcurrentHashMap<>();
groupBucketList = new ConcurrentHashMap<>();
interfaceToGroupMap = new ConcurrentHashMap<>();
groupVoMap = new ConcurrentHashMap<>();
started = false;
// Notify our interested LifecycleListeners
@@ -340,15 +347,15 @@ public class InflowControlManager implements Lifecycle, Bucket {
return svcCd;
}
public void removeAdapter(String adapter) {
public synchronized void removeAdapter(String adapter) {
adapterBucketList.remove(adapter);
}
public void removeInterface(String inter) {
public synchronized void removeInterface(String inter) {
interfaceBucketList.remove(inter);
}
public void removeGroup(String groupId) {
public synchronized void removeGroup(String groupId) {
groupBucketList.remove(groupId);
groupVoMap.remove(groupId);
// 해당 그룹에 속한 인터페이스 매핑도 제거