spring

로그추적기 사용(feat.동시성문제)

로드존슨 2023. 4. 16. 22:33
728x90

 

애플리케이션이 커지면서 점점 모니터링과 운영이 중요해지는 단계가 온다.
어디서 병목이 발생하는지???
그리고 어떤 부분에서 예외가 발생하는지를 로그를 통해 확인하는 것이 점점 중요해지고 있다.

로그를 미리 남겨뒀더라면.. 이런 부분을 손쉽게 찾을 수 있다.

 

요구사항

  • 모든 public 메서드의 호출과 응답 정보를 로그로 출력
  • 애플리케이션의 흐름을 변경하면 안됨
    • 로그를 남긴다고 해서 비즈니스 로직의 동작에 영향을 주면 안됨
  • 매서드 호출에 걸린 시간
  • 정상 흐름과 예외 흐름 구분
    • 예외 발생시 예외 정보가 남아야 함
  • 메소드 호출의 깊이 표현
  • HTTP 요청을 구분
    • HTTP 요청 단위로 특정 ID 를 남겨서 어떤 HTTP 요청에서 시작된 것인지 명확하게 구분이 가능해야함
    • 트랜잭션 ID( DB 트랜잭션 X ), 여기서는 하나의 HTTP 요청이 시작해서 끝날 때 까지를 하나의 트랜잭션이라함 

 

요구사항을 고려하여 로그를 추적할수 있게 만들었다. 다만 여기서 큰 문제가 있다.....ㅠㅠ 

 

요청시간, 호출의 깊이 를 확인할 수 있다. 다만 여기서 큰 문제가 있는데....동시성 문제이다. 

해당 save 메서드는 1초 후 저장을 할수 있게 설정을 했다.
그런데 1초안에 2번의 저장버튼을 누르게 되면 어떻게 될까?

 

 

위 예제코드에서 호출 되는 순서를 보게되면

5번스레드 호출 ㅡ> 5번 저장 ㅡ> 그리고 나서  바로 7번 스레드가 호출ㅡ>7번 저장 이 되어버린다... 

 

이와같이 쓰레드가 섞여서 나오게된다. 근데 트랜잭션id는 같다?? 

왜 그럴까? 동시성문제가 발생하기 때문이다. 

 

다른 예로 동시성 문제가 발생하는 코드를 구현한 후 코드를 설명하자면..

동시성 문제가 발생하는 코드를 구현을 설명하자면

유저 A를 저장하고 1초정도 쉰지만 저장하는 그 순간 0.1초 유저 B를 호출하는 로직이 구현된다. 

여기서 동시성 문제가 발생되어 USER-A의 저장값에 USER-B의 값이 저장된다. 

1. Thread-A 는 userA 를 nameStore 에 저장했다.

2. Thread-B 는 userB 를 nameStore 에 저장했다

3. Thread-A 는 userB 를 nameStore 에서 조회했다.

4. Thread-B 는 userB 를 nameStore 에서 조회했다

 

 

1)먼저 thread-A 가 userA 값을 nameStore 에 보관한다

2) 0.1초 이후에 thread-B 가 userB 의 값을 nameStore 에 보관한다. 기존에 nameStore 에 보관되어 있던 userA 값은 제거되고 userB 값이 저장된다.

3) thread-A 의 호출이 끝나면서 nameStore 의 결과를 반환받는데, 이때 nameStore 는 앞의 2번에서 userB 의 값으로 대체되었다. 따라서 기대했던 userA 의 값이 아니라 userB 의 값이 반환된다. thread-B 의 호출이 끝나면서 nameStore 의 결과인 userB 를 반환받는다.

 

이렇게 하나만 있는 인스턴스에
여러 쓰레드가 동시에 접근하기 떄문에 문제가 발생한다
실무에서 한번 나타나면 개발자를 가장 괴롭히는 문제도 바로 이러한 동시성 문제이다. 

USER-A  입장에서는 저장한 데이터와 조회한 데이터가 다른 문제가 발생하며 
이처럼 여러 쓰레드가 동시에 같은 인스턴스의 필드 값을 변경하면서 발생하는 문제를 동시성 문제라 한다. 

이런 동시성 문제는 여러 쓰레드가 같은 인스턴스의 필드에 접근해야 하기 때문에
트래픽이 적은 상황에서는 잘 나타나지 않고
트랙픽이 점점 많아질 수 록 자주 발생한다.

특히 스프링 빈처럼 싱글톤 객체의 필드를 변경하며 사용할 때 이러한 동시성 문제를 조심해야한다. 




왜 스프링에서 더욱 조심해야 하나? 

스프링에서 BEAN은 기본적으로 싱글톤 스코프로 등록된다. 
스프링 컨테이너가 해당 빈을 단일 인스턴스로 생성하고, 컨테이너 내에서 공유하기 때문이다. 
따라서 스프링 애플리케이션 내에서 동일한 빈을 여러 곳에서 참조하더라도 같은 인스턴스를 사용하게 된다.
이로 인해 여러 스레드가 동시에 동일한 인스턴스에 접근하여 데이터를 수정하거나 상태를 변경 때
동기화 문제가 발생할 수 있다. 

다른 프레임워크나 언어에서도 마찬가지로 ,
공유 데이터에 대한 동시 접근이 발생하는 경우에는 동시성 문제가 발생할 수 있다.
동시성 문제는 프레임워크나 언어와 관련이 있는 것이 아니라 ,
멀티 스레드 환경에서 여러 스레드가 공유 데이터에 동시에 접근할 떄 발생할 수 있는 문제이다. 
 

*참고
 이런 동시성 문제는 지역 변수에서는 발생하지 않는다. 지역 변수는 쓰레드마다 각각 다른 메모리 영역이 할당된다. 
동시성 문제가 발생하는 곳은 같은 인스턴스의 필드 또는 static 같은 공용 필드에 접근할 때 발생한다. 
동시성 문제는 값을 읽기만 하면 발생하지 않는다. 어디선가 값을 변경하기 때문에 발생한다. 

https://hohojavis.tistory.com/55

 

프로세스와 스레드의 차이

프로세스는 실행중인 프로그램이고, 스레드는 프로세스 안에서 실행하는 단위이다. 유튜브 : 프로그램 좋아요버튼,구독버튼 등 : 프로세스 프로세스는 메모리와 cpu를 프로세스마다 할당받아서

hohojavis.tistory.com

 

 

 

 

 

그렇다면 지금처럼 싱글톤 객체의 필드를 사용하면서 동시성 문제를 해결하려면 어떻게 해야할까?

이럴때 사용하는 것이 바로 쓰레드 로컬이다.

 

728x90