
안녕하세요 ! 우기의 정신차린 취준 일기입니다.
지난 시간에 이어 로그 추적기의 단점과 고쳐야 할점을 더 이어나가봐야 겠죠?
우기의 정신차린 취준일기는 3가지 문제점을 생각해보겠습니다
❤. 로그 추적기를 이용하려다보니 비지니스 로직 규모가 커졌다.
❤ . 모든 메서드에 TraceId가 추가되는 번잡함이 생겼다.
❤ . 1,2번을 해결했지만 생긴 문제점이 도출되었다. 무엇일까?
❤. 로그 추적기를 이용하려다보니 비지니스 로직 규모가 커졌다.
- 이게 도대체 무슨말일까? 예시를 한번 봐도록 해보자.


- 로그 추적기에 대한 문법이 생성됨으로써 try - catch 문이 생성되어버렸으니 로직 규모가 커졌다.
- 물론 로그 생성기가 생겨남으로써 클린코드가 지금은 해결할수 없겠지만 규모가 커진다는건 인지하고 있어야겠다.
추후에 줄일수있으면 줄여보자!
❤ . 모든 메서드에 TraceId가 추가되는 번잡함이 생겼다.
- TraceID와 Level를 동기화하려다보니 모든 메서드에 TraceId가 추가되는 번잡함이 생겼다.
동기화를 하지않으면 한번의 비지니스 흐름에 각기다른 TraceId와 일정한 Level을 갖게되는 아이러니함이 존재한다.

- 이렇게 일반적으로 Controller - Service - Repository라면 상관은없겠지만 만약 Service - Service 호출이라면?
생각만해도 끔찍하다.... 무조건 꼬일것이다... 이런 경우라면 고쳐야될 필요성을 느낀다.
- 사용자가 계속 TraceId를 들고다닐 필요없이 JVM에서 계속 관리를 해주고있다면 어떨까?
JVM에서 관리를 해준다는말은 Log 추적기 객체를 따로 만들어 JVM에서 관리할수있게끔 만들어보자..!
🧡 FieldLogTrace 와 LogTraceConfig 의 등장
💛 FieldLogTrace의 간단한 설명
- 본격적으로 동기화를하고 TraceId를 매개변수로 받지않기위해 변경된 로그 추적기
/////////////추가된 코드////////////////
private TraceId traceIdHolder; //traceId 동기화, 동시성 이슈 발생
private void syncTraceId() {
if (traceIdHolder == null) {
traceIdHolder = new TraceId();
} else {
traceIdHolder = traceIdHolder.createNextId();
}
}
private void releaseTraceId() {
if (traceIdHolder.isFirstLevel()) {
traceIdHolder = null; //destroy
} else {
traceIdHolder = traceIdHolder.createPrevId();
}
}
///////////변경된 코드/////////////////
public TraceStatus begin(String message) {
// 변경된 코드
syncTraceId();
TraceId traceId = traceIdHolder;
Long startTimeMs = System.currentTimeMillis();
log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX,
traceId.getLevel()), message);
return new TraceStatus(traceId, startTimeMs, message);
}
private void complete(TraceStatus status, Exception e) {
Long stopTimeMs = System.currentTimeMillis();
long resultTimeMs = stopTimeMs - status.getStartTimeMs();
TraceId traceId = status.getTraceId();
if (e == null) {
log.info("[{}] {}{} time={}ms", traceId.getId(),
addSpace(COMPLETE_PREFIX, traceId.getLevel()), status.getMessage(),
resultTimeMs);
} else {
log.info("[{}] {}{} time={}ms ex={}", traceId.getId(),
addSpace(EX_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs,
e.toString());
}
// 변경된 코드
releaseTraceId();
}
💚traceIdHolder
- traceId를 동기화하기위해 객체 선언
💚synctraceId method
- begin은 traceId를 생성해주고, beginSync는 있는 traceId라면 leve+1 해주는 메서드라면
그런 이유 필요없이 null이면 최초생성, null이 아니면 level+1 해주는 통합메서드
💚releaseTraceId method
release을 잘해주어야 즉, 삭제처리 및 level-1을 해주는 메서드 이 조차도 통합메서드라고 생각할수있다.
💚begin method
begin에서 먼저 생성할지 level+1할지 판단한다음 그 값을 traceId에 넣어준다
옛날 begin은 먼저 생성할지에 대한 여부의 코드였다면 현재는 begin은 통합관리가 된 코드라고 말할수있다.
💚complete method
end와 exception을 거치는 메서드, 즉 할당해제를 해줄수있는 메서드를 호출한다 (releaseTraceId method)
💛 LogTraceConfig의 간단한 설명
- 매개변수로 받는것이 아닌 JVM에서 관리를 해주어야하기때문에 만들어진 Config 파일
LogTraceConfig.java
@Configuration //Component가있음 자동으로 컴포넌트 스캔
public class LogTraceConfig {
//스프링 빈으로 등록됨 싱글톤으로 등록이되는것임
//즉 인스턴스 하나로 등록이된다는 말임
@Bean
public LogTrace logTrace(){
return new FieldLogTrace();
}
}
////////////////////////////////변경된 코드
OrderController.java
public class OrderControllerV3 {
private final OrderServiceV3 orderServiceV0;
//private final HelloTraceV2 trace;
//이제 이건 사용하지않고 스프링 빈으로 등록된것을 사용할것임
private final LogTrace trace;
💚LocTraceConfig.java
Interface로 만들어진 LogTrace를 그대로 스프링 빈으로 등록함 (싱글톤 패턴을 사용해서)
💛 OrderController의 변경점
- 매개변수에 traceId를 가지고 다니지 않아된다는 점을 확인할수있다.
public String request(String itemId) {
TraceStatus status = null;
try {
status = trace.begin("OrderController.request()");
orderServiceV0.orderItem(itemId);
trace.end(status);
return "ok";
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
💛 OrderService의 변경점
- 매개변수에 traceId를 가지고 다니지 않아된다는 점을 확인할수있다.
public void orderItem(String itemId) {
TraceStatus status = null;
try {
status = trace.begin("OrderService.request()");
orderRepositoryV0.save(itemId);
trace.end(status);
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
💚최종 결과


- 결국 사용자 임의대로 traceId를 매개변수로 담아 관리하는 모습을 보여줬지만 번잡함과 가독성의 문제, 마지막으로 호출의대한 문제가 생겨버렸다.
- 이를 두고 직접적으로 JVM에서 TraceId를 관리해줄수있게끔 관리 객체를 스프링 빈으로 등록하여 TraceId와 Level을 매개변수없이 동기화 해주는 작업을 진행하여 문제점을 해결 하였다.
❤ . 1,2번을 해결했지만 생긴 문제점이 도출되었다. 무엇일까?
💛 동시성 이슈 문제...
싱글톤 패턴으로 하나의 객체(LogTrace의 TraceId) 를 관리하는 JVM이 동시에 호출이된다면 어떻게 될것인가?
각자의 쓰레드가 알아서 해결해주지 않을까란 생각인거라면 큰오산....
쉽게 설명하자면 1초에 여러번 호출했을때 어떤식을 호출이될까? 라는생각을 가져보자

[nio-8080-exec-X] 에서 X는 쓰레드의 번호다. 1번 쓰레드 조차도 끝나지않았지만 10번 쓰레드까지 간상황이다.
💛동시성 이슈에 대한 자세한 설명

- Threa-A가 다끝나기도전에 Thread-B가 똑같은 nameStore에있는 nameA를 nameB로 강제적으로 바꾼상황이다.
이럴 경우 어떤 문제가 생기는가? 로 보았을때

- 원래 코드라면 thread-A 는 return "userA", thread-B는 return "userB"를 받아와야하지만 동시성 이슈 문제로
두 thread 모두 최근에 바꾼값을 가져오는 상황이다... 이런 문제들을 어떻게 해결할수있을지 한번 다음장에서 알아봐도록 해보자.
'🧶Spring > Tech-Stack' 카테고리의 다른 글
| [Tech-Stack] 전략 패턴(Strategy Pattern)이란 ? (2) | 2023.11.23 |
|---|---|
| [Tech-Stack] 템플릿 메서드 패턴이란 ? (1) | 2023.11.22 |
| [Tech Stack] 로그 추적기란? - 3(완료) (1) | 2023.11.22 |
| [Tech Stack] 로그 추적기란? - 1 (0) | 2023.11.20 |