굉장히 오랜만에 글을 작성하는 것 같아요. 글 쓰는 것 자체가 어색한 느낌이 드네요. :)
마지막 글을 쓴 24년 10월 이후로 많은 일들이 있었습니다. 취업도 하고, 본가에서 나와 자취도 시작하고.. 그러다보니 1분기 회고를 4월이 다 되어서야 적게 되네요.
3개월마다 회고를 적으려 했지만 한달이나 늦어버린 절 반성하면서.. 25년 1분기 회고를 시작하겠습니다.
24년 12월, 운이 좋게도 한 회사에 취업했습니다. 3개월간의 수습 기간이 있었고, 적응하는데 집중했던 것 같습니다.
신입의 경우, 수습 기간동안 무언가를 하고, 이걸 기반으로 발표를 해야 했기에 이후 2달은 발표를 위한 수습 프로젝트를 진행했습니다.
저는 AnyLink라는 인터페이스 프레임워크를 개발하는 팀에 속해 있습니다.

여러 서버, 어플리케이션, 프로토콜 간 통신을 중간에서 중개해주는 채널을 담당한다고 할 수 있을 것 같아요.
이 AnyLink에는 거래라는 개념이 있습니다. 간단하게 설명하면 중개를 진행한 하나의 요청 흐름 이라고 할 수 있을 것 같아요.

처음엔 간단한 이슈들을 진행하며, AnyLink에 대해 적응하고, 알아가는 시간을 가졌습니다.
백만줄 단위의 코드 베이스를 처음 접해보다보니 처음엔 너무 어려웠지만, 좋은 팀원분들이 많이 알려주셔서 빠르게 적응할 수 있던 것 같아요.
거래에 장애가 발생했을 시, 문제를 추적하기 위해선 로그를 확인해야 합니다.
기존 로그는 많은 정보를 알려주고 있었습니다만, 수십, 수백, 수천, 수만 TPS를 감당하는 AnyLink의 로그에서 원하는 정보만을 찾는 작업은 힘들었어요.
초당 1000개의 요청을 중개한다고 생각해봤을 때, 1000개의 거래 로그가 뒤죽박죽 찍히고 있었고, 거래의 unique한 식별자를 검색하여 로그 라인을 봐야했거든요.
이런 불편함을 해소하기 위해 트랜잭션 로그 매니저라는 거래 단위로 로그 블럭을 찍어주는 시스템을 추가했습니다.

저런 식으로 동작하는데, 거래가 시작하고 끝날 때 까지 로그를 모아놓고, 종료 시 설정에 따라 해당 이벤트들을 한번에 로그로 변경하여 찍어주는 방식을 설계하고 개발했어요.
다만, 몇가지 걱정되는 부분이 있었는데
중요한 정보를 로깅하기 위해선 코어 로직에 트랜잭션 이벤트를 생성하는 과정이 필요했고, 이 과정에서 문제가 생긴다면 어플리케이션 자체가 중단될 수 있다고 생각했습니다.
Queue에 적재된 이벤트들은 거래가 끝나는 시점에 소비되고 사라지는데, 거래가 종료되기 전 죽어버린다면 갈곳 없는 이벤트들이 Queue에 적재될테고, 결국 OOM까지 발생할 수도 있는 상황이였어요.
최대한 분석을 진행했고, 개발 이후에도 여러 오류를 내보며 테스트를 진행했음에도 마음이 놓이지 않는 부분입니다.
테스트, 그리고 거래에 대해 저보다 더 잘 아시는 QA팀의 도움을 받아 해당 문제를 체크하는 방향으로 진행될 것 같아요.
1번처럼 문제가 발생해 GC가 제대로 이뤄지지 않는 경우에도 OOM이 발생하겠지만, 정상적으로 동작함에도 문제가 발생할 수 있어요.
트랜잭션 이벤트들은 이벤트가 종료(성공 혹은 실패)되기 전까지 이벤트들을 Queue에 적재해놔야 합니다. 거래 로직이 길어질수록 이벤트는 오래 살아남게되며, 요청이 많아질수록 Old Gen까지 살아남는 늙은 객체들이 많아질거예요.
결국, 높은 TPS와 복잡한 거래가 많은 환경에선 Full GC가 더 자주, 더 많이 발생할테고, 이는 여러 시스템 상의 채널을 담당하는 AnyLink에선 큰 문제가 될 것 같았습니다.
로컬에서 1000TPS까지 테스트했을 땐 기존과 크게 차이나진 않았지만, QA에서 더 높은 수준의 스트레스 테스트를 통해 정확한 성능 측정이 필요할 것 같았고, 만약 해당 기능을 사용할거라면 Young Gen 사이즈를 조금 더 크게 가져갈 필요가 있어보였습니다.
이벤트를 소비해 로그로 변환하는 과정에선 문자열 연산이 발생합니다. 물론 가변 인자를 사용해 파라미터를 전달받고, 로깅이 가능할 때만 문자열 연산을 진행하도록 구현했지만, 로깅이라는 부가적인 로직이 주요 로직에 영향을 줄 수도 있을 것 같았어요.
이 부분 역시 로컬에서의 테스트로 확인하기엔 한계가 있었기에 QA팀의 도움을 받기로 했습니다.
발표엔 생각보다 많은 분들이 참여해 주셨습니다. 실장님, 타 팀의 팀장님, 그리고 같이 일하는 저희 팀장님과 팀원분들이 참석해주셨고, 다양한 질문을 해주셨어요.
그 중 기억나는 질문은 아래와 같았습니다.
다양한 의견들이 많았지만, 제가 내린 결론은 "영향이 미비하다" 였습니다.
하나의 Queue 객체의 생명주기는 거래가 시작하고 끝날 때 까지이고, 이와 동일한 시점에는 아무리 적어도 5개의 트랜잭션 이벤트가 발생합니다.
빈 Queue 객체의 생성 비용이 그렇게 크다고 생각하지 않았으며, 문제가 발생한다면 트랜잭션 이벤트 객체의 생성과 삭제에 의해 발생될 것이라고 생각했어요.
이 문제는 Queue에 대한 처리가 아닌 트랜잭션 이벤트 객체를 재사용하는 방식, LMAX Disruptor 혹은 트랜잭션 로그 매니저를 별도의 외부 어플리케이션으로 추출하는 방법이 필요할 것 같다고 판단했습니다.
그럴 수 있다고 생각합니다. 다만, 트레이스의 경우 어디서 문제가 발생했는지는 한 눈에 볼 수 있겠지만, 명확한 원인 파악이 힘들며, 거래량이 늘어날수록 수많은 트레이스를 조회하는 DB에 부하가 많이 가해져 대형 고객사에선 잘 사용하지 않는 상황이었습니다.
또한, 예외의 트레이스를 볼 수 없기에 로그 파일에 적재되는 엔진 로그를 봐야했는데, "트랜잭션 로그 매니저"는 엔진 로그에서 거래 흐름과 문제가 발생한 부분, 스택 트레이스를 모두 볼 수 있습니다.
특정 거래, 대상 시스템에 대해서만 로깅할 수 있도록 구현했기에 디버깅에 도움이 될 거라고 생각합니다.
아직 QA팀에서 테스트가 끝나지 않아 무슨 문제가 발생했는지 모르는 상태예요.
또한, 꽤나 큰 규모의 POC에 참여하느라 굉장히 바쁜 나날을 보내고 있습니다.
새로 구현중인 기능인 전송 유량제어(타겟 시스템으로의 유량 제어) 기능도 고민할 거리가 많아 재밌습니다.
3달 뒤 2분기 회고에선 POC를 진행하며 발생했던 문제들, 이슈를 진행하며 했던 고민들에 대해 적어볼까 합니다.
긴 글 읽어주셔서 감사합니다!
ps. 너무 오랜만에 글을 쓰니 너무 힘드네요,,, 책 좀 많이 읽어 필력을 길러야겠습니다.