Git Product home page Git Product logo

aoplocalcaching's Introduction

LocalCachingModule

AS-IS

  • ThreadLocal 을 멤버필드로 가지는 ThreadLocalCacheSupport 클래스 정의
public abstract class ThreadLocalCacheSupport<T> {
	private ThreadLocal<Map<String, T>> threadLocal = new InheritableThreadLocal<Map<String, T>>();
	
	protected T getCacheValue(String key) { ... }
	
	protected void putCacheValue(String key, T value) { ... }
	
	protected void removeCache(String key) { ... }
}
  • 캐시 토픽별로 ThreadLocalCacheSupport을 상속받는 Repository 클래스 정의
public class UserInfoRepository extends ThreadLocalCacheSupport<Object> {
	public PublicGroupInfo getUserInfo(String userId) {
		String key = getUserInfoKey(userId);
		UserInfo userInfo = (UserInfo)getCacheValue(key);
		if(userInfo == null) {
			try {
				userInfo = userInvoker.getUserInfo(userId);
				putCacheValue(key, userInfo);
			} catch (UserInvokeException e) {
				putCacheValue(key, DEFAULT_USER_INFO);
			}
		}
		return userInfo;
	}
	...
}

AS-IS 의 문제점

  • LocalCaching 사용 위해 ThreadLocalCacheSupport 를 상속받는 별도의 Repository 클래스 정의 필요
  • putCache, getCache 호출 코드의 중복
  • Cache topic 별로 개별적으로 ThreadLocal 객체를 가지게되어 메모리 낭비
  • Cache topic 별 정의한 Cache 모듈 객체들을 threadLocalRepositoryList 에 등록하여 ThreadLocalClearInterceptor에서 요청 완료후 clear 하도록 처리 필요, 관리에 어려움 (Human error발생 가능)
<beans profile="NCS">
	<util:list id="threadLocalRepositoryList">
    	<ref bean="userInfoRepository"/>
	</util:list>
</beans>

<mvc:interceptors>
        <bean class="ThreadLocalClearInterceptor">
            <property name="supportList" ref="threadLocalRepositoryList"/>
        </bean>
</mvc:interceptors>

public class ThreadLocalClearInterceptor implements HandlerInterceptor {
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
		if (CollectionUtils.isNotEmpty(supportList)) {
			for (ThreadLocalCacheSupport<?> support : supportList) {
				support.clearCache();
			}
		}
	}
}

TO-BE

  • LocalCaching 로직을 횡단관심사 모듈로 분리
@Aspect
public class LocalCacheSupport {
	private final ThreadLocal<EnumMap<LocalCacheTopic, CacheStorage>> threadLocalCache = new InheritableThreadLocal<>();
	
	@SuppressWarnings("unchecked")
	@Pointcut("@annotation(target) && execution(* *(..))")
	public void annotion(LocalCacheable target) {}
	
	@SuppressWarnings("unchecked")
	@Around("annotion(target)")
	public <T> T methodCall(ProceedingJoinPoint invoker, LocalCacheable target) throws Throwable {
		//caching logic
	}
}
  • 어노테이션을 통한 AOP 로 캐싱 적용
@LocalCacheable
public UserInfo getUserInfo(String userId) {
	return userInvoker.getUserInfo(userId);
}
  • 하나의 ThreadLocal 객체에서 모든 캐시데이터 관리

AS-IS

image

TO-BE

image

  • 2중 해싱으로 인한 성능 저하를 완화하기위해 Topic 을 Enum 으로 정의 후, Topic 맵을 EnumMap 으로 선언
//getCache
EnumMap<LocalCacheTopic, CacheStorage> cacheMapCollection = threadLocalCache.get();
Map<Key, Value> cacheMap = cacheMapCollection.get(topic);
return cacheMap.get(key);

TO-BE 의 문제점

  1. AOP 프록시 객체로 전달되는 파라미터 중 cache key 로 사용할 파라미터를 알 수 없음
public class UserInfoRepository extends ThreadLocalCacheSupport<String> {
	public String getUserDetailedInfo(String userId, String userNo) {
    	String userName;
    	try {
    		userName = getCacheValue(userId);
    		...
    	}
    }
    ...
}
@LocalCacheable
public String getUserDetailedInfo(String userId, String userNo) {
	...
}
  • 선택적 CacheKey 적용을 위해 @CacheKey 파라미터 어노테이션 추가
@LocalCacheable
public String getUserDetailedInfo(String userId, @CacheKey String userNo) {
	...
}

//LocalCacheSupport
private String generateCacheKey(Object[] args, Annotation[][] annotations) {
	//메서드 파라미터중, @CacheKey 어노테이션이 적용되어있는 파라미터만 키에 포함시킨다.
	List<Object> keyParamList = IntStream.range(0, args.length)
			.boxed()
			.filter(idx -> annotations[idx] != null)
			.filter(idx -> Stream.of(annotations[idx]).anyMatch(annotation -> annotation.annotationType() == CacheKey.class))
			.map(idx -> args[idx])
			.collect(Collectors.toList());
	
	...
}

2.public 메서드에 대해서만 캐싱 적용 가능하여 여전히 별도의 Repository 클래스 필요

//UserInfoRepository.class
@LocalCacheable
public UserInfo getUserInfo(String userId) {
	return userInvoker.getUserInfo(userId);
}

//UserInfoBo.class
@Autowired
private UserInfoRepository userInfoRepository;

private String getUserInfo(String userId) {
	...
	UserInfo userInfo = userInfoRepository.getUserInfo(userId);  
	String userName = userInfo.getUserName();
	int userAge = userInfo.getUserAge();
	...
}
  • Proxy 객체를 사용하는 Srping AOP 대신, AspectJ AOP 를 사용하여, Compile time weaving 을 통해 private method 에 localcahing 적용
//UserInfoBo.class
@Autowired
private String getUserInfo(String ownerId) {
	...
	UserInfo userInfo = getUserInfo(userId);  
   	String userName = userInfo.getUserName();
   	int userAge = userInfo.getUserAge();
	...
}

@LocalCacheable
private UserInfo getUserInfo(String userId) {
	return userInvoker.getUserInfo(userId);
}
  1. 캐시데이터에 캐시 만료 시간 적용 불가능
public List<String> get(final int userId) {
	// local cache 조회
	Map<String, Object> cacheValue = getCacheValue(String.valueOf(userId));
	if (MapUtils.isNotEmpty(cacheValue)) {
		Date curDate = new Date();
		Date createDate = (Date)cacheValue.get(KEY_CREATETIME);
		
		if (expireSeconds <= 0 || curDate.before(DateUtils.addSeconds(createDate, expireSeconds))) {
			return (List<String>)cacheValue.get(KEY_EXTENSIONS);
		}
	}
	...
}
  • 어노테이션 파라미터로 만료시간 설정할 수 있는 기능 제공
@LocalCacheable(expireTime = 15000L)
private UserInfo getUserInfo(String userId) {
	return userInvoker.getUserInfo(userId);
}

성능 테스트

테스트 표본

  • Caching 클래스를 이용한 캐싱
  • AspectJ AOP 를 이용한 캐싱

테스트 설계

  • max cache size : 100
  • case 1 : Cache Hit 만 발생
    • getCache 만 수행했을때의 성능 비교
  • case 2 : Cache Miss 만 발생
    • getCache + setCache 수행했을때의 성능 비교

테스트 결과 - 1

시나리오 캐시 적중률(%) 캐싱 종류 평균 수행 시간(ms) 비고
1 100 aop 10.08
direct 1.98
2 0 aop 11.04
direct 2.69

테스트 결과 분석 - 1

  • Aop 를 이용한 캐시가 Direct 캐시에 비해 약 5배 정도 성능이 떨어짐
  • Aop 캐시 로직의 각 단계별로 시간 측정 결과
    • key 생성 : 0.0785
    • threadLocal에서 cache 조회 : 0.0142
    • threadLocal 에 cache 설정 : 0.0181
  • key 생성 로직에서 많은 부하 발생
    • 메서드의 전체 파라미터 탐색
    • stream 을 이용한 loop
    • reflection 을 통한 Method signature 및 파라미터 조회
//메서드 파라미터중, @CacheKey 어노테이션이 적용되어있는 파라미터만 키에 포함시킨다.
List<Object> keyParamList = IntStream.range(0, args.length)
		.boxed()
		.filter(idx -> annotations[idx] != null)
		.filter(idx -> Stream.of(annotations[idx]).anyMatch(annotation -> annotation.annotationType() == CacheKey.class))
		.map(idx -> args[idx])
		.collect(Collectors.toList());

//@CacheKey 어노테이션이 적용된 파라미터가 없다면, 전체 파라미터를 키에 포함시킨다.
return StringUtil.format(keyForamt, keyParamList.isEmpty() ? args : keyParamList.toArray());
  • key 생성 로직에서 stream 이 아닌, for-each 문으로 loop 하도록 수정
for(int idx = 0; idx < args.length; idx++) {
	if(annotations[idx] != null) {
		for(Annotation annotation : annotations[idx]) {
			if(annotation.annotationType() == CacheKey.class) {
				keyParamList.add(args[idx]);
				break;
			}
		}
	}
}

테스트 결과 - 2

시나리오 캐시 적중률(%) 캐싱 종류 평균 수행 시간(ms) 비고
1 100 aop 3.75
direct 1.95
2 0 aop 5.28
direct 2.49

테스트 결과 분석 - 2

  • 성능 측정결과 Aop 캐시의 성능이 2배 이상 향상
  • Aop 캐시 로직의 각 단계별로 시간 측정 결과 key 생성 로직의 소요시간이 2배 이상 줄어듬
    • key 생성 : 0.0316
    • threadLocal에서 cache 조회 : 0.0162
    • threadLocal 에 cache 설정 : 0.0226

테스트 결과 정리

  • Aop Cache 는 Direct Cache 에 비해 약 2배정도의 시간 소모
    • 컴파일 타임 위빙으로 프록시 객체로 인한 성능저하는 없음
    • 1번의 해싱만 하면 되는 Direct Cache 에 비해 Aop Cache 는 2번의 Hashing 이 필요하여 약간의 성능 저하 발생

      Topic 구분용 Map 을 EnumMap 으로 사용하여 2번의 Hashing 으로 인한 성능저하는 거의 없을것으로 예상

    • Cache Key 생성 과정에서 메서드 전체 파라미터 탐색이 수행되어 결정적인 성능 저하 발생
  • Cache Key 생성 로직을 수정하여 추가적은 성능 개선 가능
  • Aop Cache 를 통해 얻는 이득(중복코드 제거, 사용의 편리, 개발 시간 절약) 과 손실(성능 저하) 을 따져본뒤 적용 필요

aoplocalcaching's People

Contributors

jisoooh94 avatar

Stargazers

 avatar

Watchers

 avatar  avatar

aoplocalcaching's Issues

[feat] ThreadLocalCache 모듈 Cache key 어노테이션 적용

요구사항

  • 어노테이션으로 ThreadLocalCaching 적용한 메서드의 파라미터중 일부 파라미터만 어노테이션으로 선택하여 Cache key 에 사용하도록 적용

Features

  • Cache key 어노테이션 추가
  • 메서드 파라미터 읽어와 Cache key 어노테이션이 적용된 파라미터만 추출

[feat] Cache data expire 기능 추가

요구사항

  • 캐시된 데이터에 만료 시간을 적용,
  • Cache hit 되었더라도, 해당 데이터가 만료되었으면 Cache miss 로 판단, 캐시 갱신 수행

Features

  • LocalCacheable 어노테이션에 expireTime 필드 추가
  • 어노테이션의 expireTime 파라미터가 0이 아닐경우, 캐시 조회시 creationTime 체크 수행, 데이터 캐싱시 creationTime 도 함께 캐싱

See also

[feat] AspectJ aop 적용

요구사항

  • 서비스 소스 분석결과, private method 에도 LocalCaching 적용이 가능해야하는 요구사향 확인
  • 기존의 Spring AOP 로는 private method 에 대한 위빙이 불가능하여, AspectJ 를 이용한 AOP 로 대체
  • AspectJ 컴파일러로 기존 서비스 소스 빌드했을떄 발생할 수 있는 사이드 이펙트 조사 필요

Features

  • AspectJ AOP 를 위한 의존성 추가
  • AspectJ 컴파일러 설정 및 그에따른 사이드이펙트 버그 수정

See also

[test] 성능 테스트

성능 테스트

테스트 표본

  • Caching 클래스를 이용한 캐싱
  • AspectJ AOP 를 이용한 캐싱

테스트 설계

  • max cache size : 100
  • case 1 ; Cache Hit 만 발생
    • getCache 만 수행했을때의 성능 비교
  • case 2 : Cache Miss 만 발생
    • getCache + setCache 수행했을때의 성능 비교

테스트 결과 - 1

시나리오 캐시 적중률(%) 캐싱 종류 평균 수행 시간(ms) 비고
1 100 aop 10.08
direct 1.98
2 0 aop 11.04
direct 2.69

테스트 결과 분석

  • Aop 를 이용한 캐시가 Direct 캐시에 비해 약 5배 정도 성능이 떨어짐
  • Aop 캐시 로직의 각 단계별로 시간 측정 결과
    • key 생성 : 0.0785
    • threadLocal에서 cache 조회 : 0.0142
    • threadLocal 에 cache 설정 : 0.0181
  • key 생성 로직에서 많은 부하 발생
    • 메서드의 전체 파라미터 탐색
    • stream 을 이용한 loop
    • reflection 을 통한 Method signature 및 파라미터 조회
//메서드 파라미터중, @CacheKey 어노테이션이 적용되어있는 파라미터만 키에 포함시킨다.
List<Object> keyParamList = IntStream.range(0, args.length)
		.boxed()
		.filter(idx -> annotations[idx] != null)
		.filter(idx -> Stream.of(annotations[idx]).anyMatch(annotation -> annotation.annotationType() == CacheKey.class))
		.map(idx -> args[idx])
		.collect(Collectors.toList());

//@CacheKey 어노테이션이 적용된 파라미터가 없다면, 전체 파라미터를 키에 포함시킨다.
return StringUtil.format(keyForamt, keyParamList.isEmpty() ? args : keyParamList.toArray());
  • key 생성 로직에서 stream 이 아닌, for-each 문으로 loop 하도록 수정
for(int idx = 0; idx < args.length; idx++) {
	if(annotations[idx] != null) {
		for(Annotation annotation : annotations[idx]) {
			if(annotation.annotationType() == CacheKey.class) {
				keyParamList.add(args[idx]);
				break;
			}
		}
	}
}

테스트 결과 - 2

시나리오 캐시 적중률(%) 캐싱 종류 평균 수행 시간(ms) 비고
1 100 aop 3.75
direct 1.95
2 0 aop 5.28
direct 2.49

테스트 결과 분석

  • 성능 측정결과 Aop 캐시의 성능이 2배 이상 향상
  • Aop 캐시 로직의 각 단계별로 시간 측정 결과 key 생성 로직의 소요시간이 2배 이상 줄어듬
    • key 생성 : 0.0316
    • threadLocal에서 cache 조회 : 0.0162
    • threadLocal 에 cache 설정 : 0.0226

테스트 결과 정리

  • Aop Cache 는 Direct Cache 에 비해 약 2배정도의 시간 소모
    • 컴파일 타임 위빙으로 프록시 객체로 인한 성능저하는 없음
    • 1번의 해싱만 하면 되는 Direct Cache 에 비해 Aop Cache 는 2번의 Hashing 이 필요하여 약간의 성능 저하 발생

      Topic 구분용 Map 을 EnumMap 으로 사용하여 2번의 Hashing 으로 인한 성능저하는 거의 없을것으로 예상

    • Cache Key 생성 과정에서 메서드 전체 파라미터 탐색이 수행되어 결정적인 성능 저하 발생
  • Cache Key 생성 로직을 수정하여 추가적은 성능 개선 가능
  • Aop Cache 를 통해 얻는 이득(중복코드 제거, 사용의 편리, 개발 시간 절약) 과 손실(성능 저하) 을 따져본뒤 적용 필요

[feat] Spring batch 환경 세팅

요구사항

  • Spring batch 적용을 위한 기본 환경 세팅

Features

  • Spring batch 기본 환경 세팅
  • Hello world batch 프로젝트 개발

See also

[feat] ThreadLocalCache 모듈 개발

요구사항

  • 제네릭한 ThreadLocalCacheSupport 클래스 구현
  • AOP 를 통한 선언적 캐싱 적용 구현

Features

  • ThreadLocalCacheSupport 클래스 설게
  • 어노테이션을 통한 선언적 AOP 를 이용해 ThreadLocalCaching 적용

[v0.1] 프로젝트 개발환경 구축

요구사항

  • Spring 웹어플리케이션 프로젝트 개발을 위한 초기 환경을 구축한다.

Features

  • MVC 패턴 기반 Spring 프로젝트 개발
  • log4j2 + slf4j 적용
  • Profile + Property 주입 적용
  • test code 작성 환경 구축
  • 임베디드 database 구축

[v0.2] Spring AOP 테스트

요구사항

  • 선언적, 명시적 AOP 적용 테스트
  • 다양한 포인트컷, 조인포인트 테스트

Features

  • 명시적 AOP 테스트
    • 포인트컷 비교 테스트
    • 조인포인트 비교 테스트
  • 선언적 AOP 종류 테스트
    • ProxyFactoryBean
    • AOP 네임스페이스
    • @AspectJ어노테이션
  • 선언적 AOP 테스트
    • 포인트컷 비교 테스트
    • 조인포인트 비교 테스트
  • JDK프록시, CGLIB 프록시 테스트
  • AOP 인트로덕션 테스트

[feat] ThreadLocaclCacheInterceptor 책임 분리 및 Common caching 개발

요구사항

  • 별도의 EnumMap 지정 없이 직접 캐싱하는 api 필요
  • 직접 캐싱 api 추가를 위해 ThreadLocalCacheInterceptor 의 AOP 로직과 캐싱 로직 분리

Features

  • ThreadLocalCacheInterceptor 에 포함되어있는 로컬캐싱 로직들을 별도의 클래스로 분리
  • ThreadLocalCaching 모듈에 common caching api 추가

See also

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.