몇 분전 페이스북 눈팅을 하다가 생활코딩의 한 멤버분이 리눅스 서버의 top 스냅샷을 올려서 조언을 구하는 내용이 있길래 얼핏 보니

스왑메모리를 2기가 정도 사용하고 있고 서버 리부팅후 일정 시간이 지나면 서버가 느려진다라는 말로 보아 거의 80% 이상 서버 로직

메모리 누수로 판단이 되는데 예전에 정리했던 내용을 리마인드겸 한번 공유하면 도움이 될까 싶어서 포스팅을 하기로 한다.


  • 설명
    • 리눅스는 free 메모리를 최대한 cache 메모리로 사용하여 시간이 지날수록 cache 메모리 사용량이 증가하면서 애플리케이션의 로딩 속도가 향상됨.
      • 물론 free 메모리가 필요한 순간에는 cache 메모리를 줄이고 free 메모리를 확보함.
  • top 예제

    • 위 그림에서 used는 실제 메모리 사용량이 아니라 빨간색으로 묶은 buffers + cached, 그리고 여기에 표시되지 않은 다른 메모리사용량(실제 메모리 사용량)을 합한 값으로 보여지게 된다.

  • free 예제


    • 실제 메모리 사용량을 확인할 수가 있는데, 빨간색으로 보여지는 부분의 used가 실제 메모리 사용량이고 free가 실제 여유 메모리를 나타낸다.
      • 첫번째 라인의 used는 buffers + cached + 실제 메모리사용량 --> 1420 = 167 + 1072 + 180 (mb로 표시하여 1의 오차가 발생, free -k로 보면 값이 정확함)
      • 두번째 라인의 free는 첫번째 라인의 free + buffers + cached --> 1845 = 606 + 167 + 1072 (mb로 표시하여 1의 오차가 발생, free -k로 보면 값이 정확함)


업무와 관련해서 비지니스 로직 응답 속도 개선을 위해 만든 병렬처리모듈을 소개한다.
아래 모듈을 실무에 적용하여 최대 1/4 정도의 응답 속도 감소 효과를 볼 수 있었다. (4s -> 1s)

1. 개요
- 선후 관계가 없는 비지니스 로직의 처리 응답 속도를 개선하기 위해 자바 concurrency 및 reflection을 이용해 병렬로 처리하는 모듈 개발.

2. 소스 레파지토리 : https://github.com/khparkDev/ParallelExecutor
- 응답을 받을 수 있는 Callback Executor와 응답을 받지 않는 DefaultExecutor 두 클래스로 구분해놓았다.(TestMain.java에 사용 방법을 기술해놓았음)

3. 주요 로직
* 작업 추가 및 실행

 
protected void executeWorkerThread() {
	executor = Executors.newFixedThreadPool(taskMap.size());
	taskMap.entrySet().parallelStream().forEach(
			tm -> {
				futureMap.put(tm.getKey(), executor.submit(new ParallelWorkerThread(taskMap.get(tm.getKey()))));
				executorLogger.setStartTimeTask(tm.getKey());
			});
	futureMap.entrySet().parallelStream().forEach(
			fm -> {
				try {
					result.put(fm.getKey(), futureMap.get(fm.getKey()).get(TIMEOUT_SEC, TimeUnit.SECONDS));
					executorLogger.setEndTimeTask(fm.getKey());
				} catch (Exception e) {
					LOGGER.error("# executeParallelTask : ", e);
				}
			});
}
* 실제 작업 수행 쓰레드 클래스
 
public Object call() throws Exception {
	Method[] classMethods = classObject.getClass().getDeclaredMethods();
	List methodList = Stream.of(classMethods).parallel()
			.filter(method -> method.getName().contains(methodName) && method.getParameterTypes().length == taskParamClassType.length)
			.collect(Collectors.toList());

	if (taskParamClassType.length == 0) {
		return methodList.get(0).invoke(classObject);
	}

	for (Method targetMethod : methodList) {
		Class[] methodParams = targetMethod.getParameterTypes();

		for (int i = 0; i < taskParamClassType.length; i++) {
			String taskParamTypeName = taskParamClassType[i].getSimpleName().toLowerCase();
			String methodParamTypeName = methodParams[i].getSimpleName().toLowerCase();

			if (taskParamTypeName.contains(methodParamTypeName)) {
				targetMethod.setAccessible(true);
				return targetMethod.invoke(classObject, taskParam);

			}
		}
	}

	return EMTPY_OBJECT;
}
4. 실행 결과 - 개별 작업들의 실행 시간 출력 및 가장 오래 걸린 시간(실제로 병렬처리가 수행된 시간)을 보여준다

[2015-11-09 20:43:02] [DEBUG] TestMain.main[24]
[2015-11-09 20:43:02] [DEBUG] SampleService.testService[34] testService = {true, C, 1, 1}
[2015-11-09 20:43:02] [DEBUG] SampleService.testService[24] testService = {msg = message test!}
[2015-11-09 20:43:02] [DEBUG] SampleService.testService[13] testService
[2015-11-09 20:43:02] [DEBUG] SampleService.testService[29] testService = {10, 12.2,345.123}
[2015-11-09 20:43:02] [DEBUG] SampleService.testService[19] testService = {model.getName() = testName}
[2015-11-09 20:43:02] [DEBUG] ExecutorLogger.prettyPrint[54]
--------------------------------------------------
taskName = testService0 , execute time = 100 ms
taskName = testService2 , execute time = 100 ms
taskName = testService1 , execute time = 140 ms
taskName = testService4 , execute time = 100 ms
taskName = testService3 , execute time = 100 ms
--------------------------------------------------
Total Execute time = 140 ms
--------------------------------------------------
일종의 개발자 유희

     - 간접자기참조에 대해 광범위하게 연구한 미국의 철학자 윌러드  밴 오먼 콰인의 이름을 따서 지음. 별도의 입력없이 자기 자신을 출력할 수 있는 프로그램.

     - 다양한 언어로 작성된 Quine 소스를 볼 수 있는 곳 : http://c2.com/cgi/wiki?QuineProgram

     - 윌러드 밴 오먼 콰인 


+ Recent posts