접속자들이 동시에 조회 또는 등록을 하는 작업을 했을 떄 동시성이 꺠졌습니다. 일반적인 상황에서는 일어나지 않고, 여러 개의 프로시저를 순차적으로 진행하는 메서드에서 프로시저들 사이에 타 접속자가 로그인 할 시 해당 정보가 덮어 씌워지면서 문제가 발생했습니다. 프로시저는 소요되는 시간이 초 단위로 길다 보니 더 쉽게 이러한 결과가 자주 나타난 듯 했습니다.
Service단이 ServiceExecutor를 상속 받아 공통된 정보를 조회하도록 설계 된 구조였습니다. 여기서 문제점이 Map형식으로 되어있는 공통코드 변수가 전역변수로 선언되어져 있었습니다. 전역변수에 수정 또는 삭제가 타 접속자의 로그인으로 인해 발생했을 때 변경된 데이터로 다음 프로시저가 작동되는 문제였습니다. 이전의 타 언어로 작성된 프로그램을 컨버팅하여 마이그레이션 하다보니 구조적으로 문제가 발생한 것으로 보여졌습니다. 또한 요구되는 비즈니스 로직에도 변경이 있어서 이전에는 발생하지 않았던 오류가 확인 된 것으로 보여졌습니다. (이전에도 존재했던 구조적 결함이었으나 오류가 발생될 수 있는 사례가 없었던 것 같습니다.)
우선 적용된 해결책은 전역변수를 직접 사용하는 부분을 제거하고, 지역변수로 데이터를 받아오도록 하는 것이었습니다. 덕분에 프로시저에 해당하는 동시성 문제는 해결 되었습니다.
그렇다면 정말로 동시성 문제가 사라졌는가에 대해서는 의문이 남았다. 동시 접속자가 많지 않은 프로그램이고, 일반 조회의 경우 소요시간이 매우 짧기 때문에 동시성 문제가 발생할 확률은 현저히 낮아 보였습니다. 그러나 여러 부분에서 문제점이 많은 설계와 코드라고 느껴졌습니다. 우연과 우연이 겹치면 일반적인 CRUD 메서드에서도 충분히 오류가 발생할 가능성이 있다고 판단되었습니다.
동시성 방어 원칙
위의 방법들에서는 '자료사본을 사용하라'를 통해 문제를 해결 할 수 있었습니다. 추가적으로 ConcurrentHashMap를 적용하는 해결책도 가능성이 있어 적용해봤으나 Null값이 있는 경우가 사용할 수 없는 방식이어서 불가했습니다.
임시 방책으로 문제는 해결 했으나, 결국 처음에 얼마나 정교한 설계를 구현해 놓았는가가 무었보다 중요하다는 사실을 다시 느끼게 되었습니다. 프로젝트가 진행 될 수록 새로운 코드가 추가되어질 수 밖에 없고, 하나의 코드가 변경될 때 갖게 되는 파급효과가 상당하다는 사실을 느낄 수 있었습니다. 사실 STS를 사용하고, Spring 기능을 일부 구현했을지는 모르지만 현재 진행하고 있는 스프링 프레임워크 철학을 녹여낸 코드와는 매우 거리가 있지 않나 싶었습니다.
또한, 테스트이 중요성을 더 크게 체감하게된 경험이었습니다. 아무리 좋다고 판단되는 코드이 변형을 진행하고 성공하더라도 나와 동료들을 설득하기 위해서는 안정성이 그 무엇보다 중요하다는 생각이 들었습니다. 그리고 안정성을 수치화 하고 시각화 할 수 있는 사실상 유일한 방법은 테스트 뿐이고 그래서 개발에 있어서 TDD가 필수임을 더 크게 마음에 새길 수 있었습니다.