
안녕하세요 ! 우기의 정신차린 취준 일기입니다.
지난 시간의 로그 추적기의 단점 ! 인스턴스를 사용하여 동기화하려다보니 중복되는 값이 생기는 에러가있었죠?
그 단점들을 해결하는 과정들을 보여드리겠습니다..!
❤. 심각한 동시성 이슈 문제
❤. ThreadLocal의 등장
❤. 내가 생각한 아직 해결하지 못한 문제
❤. 심각한 동시성 이슈 문제
💛. 동시성 이슈 문제..? 쉽게 생각하고 쉽게 해결할순없을까?
- 여태까지 로그추적기는 잘만들었다. 작동도 잘되고 값까지 잘 출력되는걸 볼수있었다. 동시성 문제라고 하니 잘 와닿지 않는다. 프로젝트를 예시로 들어서 쉽게 설명해보겠다.
- FieldLogTrace는 싱글톤으로 등록된 스프링 빈이다. 이 객체의 인스턴스가 어플리케이션에 딱 하나만 존재한다는것이다.이렇게 하나만 있는 인스턴스의 FieldLogTrace의 traceIdHolder 필드를 여러 쓰레드가 동시에 한번 접근한다면 어떨까?


- 결국 여러 쓰레드가 동시에 같은 인스턴스의 필드 값을 변경하면 발생하는 문제를 동시성 문제라고 일컫는다.
Tip -
동시성 문제는 지역변수에서는 발생하지않는다. 지역 변수는 쓰레드마다 각각 다른 메모리 영역을 차지하고있다.
동시성 문제는 인스턴스의 필드(주로 싱글톤에서 발생) 또는 static같은 공용필드에 접근할때 발생한다.
동시서 문제는 값을 읽기만 하면 발생하지않는다. 어디선가 값을 변경하기 때문에 발생한다.
❤. ThreadLocal의 등장
💛. ThreadLocal? 너 ... 뭔데 ?
ThreadLocal이란 해당 쓰레드만 접근할수있는 특별한 저장소를 말한다. 쉽게 물건 보관 창고라고 생각하자.
사용자별로 물건 보관 창고가 존재하고, 창구직원은 사용자별로 물건을 보관하고 찾아준다.



💛Thread Local의 존재의 가치는 무엇인가?
ThreaLocal, 왜 필요한가? 당연한 질문, 그렇지 못한 답변이 될순없다.
인스턴스의 필드값을 여러 쓰레드가 동시에 호출한다면 필드값이 올바르지않게 작용될수있다.(동시성 이슈) 여러 쓰레드가 각 필드값을 각각 사용할수있게 저장소를 따로 만들어주는 역할이 ThreadLocal역할! ThreadLocal이 존재한다면 여러쓰레드가 동시에 호출하여도 각자 똑같은 역할을 수행할수있다.
🧡.ThreadLocal 적용해보자.
💛ThreadLocalLogTrace
- 완전한 코드가 아닌 기본코드에서 수정된 코드만을 보여준다.
public class ThreadLocalLogTrace implements LogTrace {
private static final String START_PREFIX = "-->";
private static final String COMPLETE_PREFIX = "<--";
private static final String EX_PREFIX = "<X-";
//private TraceId traceIdHolder; //traceId 동기화, 동시성 이슈 발생
private ThreadLocal<TraceId> traceIdHolder = new ThreadLocal<TraceId>();
@Override
public TraceStatus begin(String message) {
syncTraceId();
TraceId traceId = traceIdHolder.get();
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();
}
private void releaseTraceId() {
TraceId traceId = traceIdHolder.get();
if (traceId.isFirstLevel()) {
traceIdHolder.remove(); //destroy
} else {
traceIdHolder.set(traceId.createPrevId());
}
}
private void syncTraceId() {
TraceId traceId = traceIdHolder.get();
if (traceId == null) {
traceIdHolder.set(new TraceId());
} else {
traceIdHolder.set(traceId.createNextId());
}
}
}
💚 ThreadLocal<TraceId> traceIdHolder
ThreadLocal 객체를 생성해줌으로써 저장소를 만들어주는 역할을 만든다.
💚 begin, complete, release, syncTraceId method
기존 method에서 추가 메서드만 존재한다..
.get() : 메서드를 이용해서 현재 쓰레드의 로컬 변수 값을 저장한다.
.set() : 메서드를 이용해서 현재 쓰레드의 로컬 변수 값을 읽어온다.
.remove() : 메서드를 이용해서 현재 쓰레드의 로컬 변수값을 삭제한다.
💛OrderController, OrderService, OrderRepository
- 별 변경사항없이 진행할수있었다.
🧡결과와 회고
왼쪽은 동시성 이슈문제가 생긴 문제, 오른쪽은 ThreadLocal로 동시성 문제를 해결한 부분이다.


오른쪽은 동시성 이슈를 해결하였다. [d5cbd813] 이라는 traceId가 실행한 다음 [f0ce5814]도 똑같이 실행되지만 각기 다른 Thread에서 실행이 되고있다는걸 순차적으로 보여주고있다.
ThreadLocal을 사용하면 동시성 문제를 해결할수있다는걸 보여줌으로써 객체를 싱글톤패턴을 적용함으로써 인스턴스의 필드값을 고정적으로 가져간다면 동시성 문제가 생길수 있다는 기술지식까지 알아보는 계기가 되었다.
❤. 내가 생각한 아직 해결하지 못한 문제
로그 추적기 -1 편에서도 똑같이 말한것이 있다. 그렇다면 Controller, Service, Repository단에 존재하는 로그 추적기가계속 존재해야하는가? 의문감이 든다. 비지니스 로직이 너무 더러워지는게 아닌가? 생각이든다고 생각한다.

그저 orderServiceV0.orderItem(itemId)만이 핵심 로직, 나머지는 보조 로직으로써 로그만을 보여주는 로직이라고 생각한다. 이 핵심로직과 보조 로직을 나눌수는없을까? 라는 생각이 들음으로써 마지막으로 이 로그 추적기에대한 블로그 글을 마치도록 하겠다.
추가적으로 핵심로직과 보조 로직을 나눌수있는 방법을 찾아본다면 로그 추적기가 훨씬 더 간편하게 만들어보겠다.
'🧶Spring > Tech-Stack' 카테고리의 다른 글
| [Tech-Stack] 전략 패턴(Strategy Pattern)이란 ? (2) | 2023.11.23 |
|---|---|
| [Tech-Stack] 템플릿 메서드 패턴이란 ? (1) | 2023.11.22 |
| [Tech Stack] 로그 추적기란? - 2 (1) | 2023.11.21 |
| [Tech Stack] 로그 추적기란? - 1 (0) | 2023.11.20 |