본문 바로가기
생각 정리

3개월 동안 진행한 핵심 도메인 리팩터링과 새벽 중단 배포 회고

by 타태 2023. 8. 19.

 

 
 
2023.08.19 - [공부 기록] - [NEXTSTEP] ATDD 과정 4주차 피드백, 5주차 시작[끝

[NEXTSTEP] ATDD 과정 4주차 피드백, 5주차 시작[끝]

2023.07.29 - [공부 기록] - [NEXTSTEP] ATDD 과정 3주차 피드백, 4주차 시작 [NEXTSTEP] ATDD 과정 3주차 피드백, 4주차 시작 2023.07.22 - [공부 기록] - [NEXTSTEP] ATDD 과정 2주차 피드백, 3주차 시작 [NEXTSTEP] ATDD 과정

ktae23.tistory.com

 
4월 말부터 시작하여 8월 중순까지 약 100일 동안 핵심 도메인 리팩터링을 진행했다.
이 과정에서 경험한 내용과 아쉬운 점들을 돌아보기 위해 회고를 작성한다.
 
먼저, 우리의 서비스는 최초에 특정 사용자만을 위해 만들어졌다.
때문에 핵심 도메인을 비롯한 모든 기능은 핵심 사용자만을 위해 존재했고 모든 관계 설정은 해당 사용자가 식별 조건이었다.
 
하지만 서비스가 확장되면서 다른 사용자 유형이 추가 되었고, 핵심 사용자의 기능을 공유하기 시작했다.
그러다 보니 핵심 사용자를 위한 기능을 다른 사용자 유형이 빌려 쓰는 상황이 되었는데 문제는 다른 사용자 유형의 수가 핵심 사용자 수를 압도하기 시작한 것이다.
 
그래서 우리의 다음 목표를 위해, 그리고 현재 사용자의 경험을 개선하기 위해 핵심 도메인을 개편하기로 했다.
목표는 핵심 사용자와 주변 사용자가 각각 관리하던 약 70만 건의 데이터를 일관된 기준으로 연동하여 두 사용자가 하나의 데이터를 바라보고 사용할 수 있도록 하는 동시에 1:N관계였던 데이터를 N:M으로 개편하는 것이었다.
 


 
이를 위해 하나의 테이블을 추가하고 4개의 테이블을 수정 했으며 약 1만 2천 라인의 코드가 추가되고 약 6천 라인의 코드를 제거했다.
사용관계가 N:M이 되면서 기존에 비해 비즈니스 로직의 복잡도가 급증했지만 개념적 단순함(하나만 사용하던걸 여러 개 사용하게 된다)으로 인해 어떤 정책적인 기준이 명확하지 않은 상태였다.
 
심지어 서비스가 처음 만들어졌던 2년여 전에 작성된 로직에 대한 리팩터링이라 테스트 코드가 없는 부분들도 많았지만 리팩터링에 필수적으로 선행되어야 하는 테스트 작성을 하지 못한 채로 시작하여 굉장히 불안정한 작업이었다.
 
나 역시 해당 로직에 대해 제대로 열어 본적이 없었고 히스토리를 파악하고 있는 동료가 없어서 코드를 차근차근 읽어나가며 기존 동작을 파악하는 수밖에 없었다.
 
 

이 과정에서 코드를 도식화하고 도메인을 추출하여 리버스 이벤트 스토밍을 하기도 했다.
개발팀 외의 동료들도 우리 서비스가 어떤 프로세스를 갖고 있는지 보고 알 수 있으면 좋겠다는 생각이었으나 모든 로직을 정리하기엔 시간이 너무 부족했고 도식화한 결과도 보고 쉽게 이해하기에는 적합하지 않았다.
그래서 내가 작업하기에 필요한 만큼만 진행하고 중단했다.
 
 


 
 

 
기간에 대한 압박, 인수 조건 없는 목표, 데이터 정합성에 대한 부담 때문에 굉장히 예민하고 심리적으로 힘들었던 시간이었다. "내가 이거 데이터 잘못 꼬아버리면 너무 크리티컬 하지 않나? 신뢰를 잃고 사용자들이 이탈하면 어떡하지?" 하며 굉장한 압박감을 느끼고 있었다.
 
때문에 구조 변경 이후 기존 데이터에 대한 처리는 버저닝을 통해 기존 버전의 사용자는 필수로 특정 api에 요청을 하도록 하여 기존 데이터를 마이그레이션 해주는 로직을 작성했었다. 이를 통해 약 1~2년 간 활성 사용자는 버전을 올리며 스스로의 데이터를 갱신할 것이고 비활성 사용자는 개인정보를 처리하는 과정에서 삭제될 데이터이니 구태여 마이그레이션을 하지 않아도 될 일이었다.
 
하지만 이 작업은 우리 서비스의 목표지점에 가기 위한 전초 작업에 불과했기 때문에, 기존 데이터를 끌고 가며 유지하고 관리할 수 없었다. 그래서 나는 데이터 마이그레이션을 위한 약 1천 줄의 DDL, DML과 결과를 검증할 쿼리를 작성했고 운영 환경의 데이터베이스를 복제하여 5번 정도 실데이터에 테스트를 하며 프로시져로 쿼리를 대체하고 유니크 키를 잠시 추가하여 insert ignore 구문을 사용하고 키를 제거하는 등의 방식을 활용하여 최적화했고 이후 스냅샷을 폐기처리하고 실전을 기다렸다.
 
첫 번째 테스트를 통해 확인한 일반 쿼리 수행 예상 시간은 4시간이었으나 여러 트릭을 활용해 최적화 한 뒤는 20여분 안에 모든 쿼리가 수행 될 수 있었다.
 

728x90

 
 

 
서버 점검을 올리고 새벽에 중단 배포를 할 예정이었기 때문에 백엔드팀 내부에서는 이날을 DOOMS DAY, 심판의 날이라 불렀다.
그렇게 결전의 날이 다가왔고 서버 점검이 올라간 이후 준비한 순서대로 작업은 순조롭게 진행되었다.
나는 RDS 스케일업, 스냅숏 생성, 쿼리 수행을 진행했고 동료는 배치 인스턴스 스케일업과 젠킨스 마이그레이션을 진행했다.
 
이외에도 엘라스틱 빈스톡의 플랫폼을 JAVA 8에서 11로 올리면서 인스턴스 유형을 변경하여 비용을 유지하면서 성능을 향상시키는 작업도 진행했는데, 이건 미리 해두고 ROUTE53에서 레코드 네임를 옮겨주는 방식으로 사전에 처리해 둬서 배포 당일에는 신경 쓰지 않아도 되었다.
보통의 경우 JAVA 8 버전은 11로 숫자만 올려도 별문제 없이 지원이 되기 때문에 문제 될 것은 없었다.
 
8 버전이 11 버전보다 훨씬 오래 지원되는 버전이고 11 버전으로 올리면서 얻을 수 있는 실질적인 이점은 컬렉션의 of와 같은 정적팩토리 메서드나 Optioanl에서의 ifPresentElse와 같은 메서드처럼 편의성이 더 늘어났다는 것, 그리고 GC 방식 변경 말곤 없기에 현시점에서 굳이 올릴 이유를 못 느끼고 있었다.

하지만 채용 진행 할 때 면접자분들이 왜 8을 쓰냐고 묻는 게 귀찮아서라도 하는 김에 올려야겠다 생각해서 올렸다.
정말 단지 그뿐이다.
 


 
배포 자체는 큰 문제 없이 수행 되었는데 이와 별개로 풀어야 할 문제가 하나 있었다.
이번 작업을 통해 엮어주는 데이터 중에서는 새로 생성되는 row들이 있는데, 이와 관련한 에그리것을 생성하는 조건이 까다로워서 쿼리로 만들기엔 한계가 있었다.
약 40만 건으로 예상되기 때문에 40만건 X 10~30건의 row를 각 row마다 조건에 따라 다른 값을 삽입했어야 했다.
고민을 했다. Batch를 돌릴까? 아니야, 너무 시간이 오래 걸리고 배치 프로그램 작성 할 시간마저 부족해. 그럼 필요 데이터가 없는 경우 요청할 생성 api를 심을까? 흠.. 그럴 거면 애초에 버저닝을 했지.
 
고민 끝에 SQS를 활용하여 새벽배포시간 이후에 생성된 root domain의 row들 중 에그리것이 생성되지 않은 것에 대해 생성해 주는 큐를 발행하기로 했다.
이 경우에는 원래 없던 데이터가 생기는 영역이었기 때문에 사용자 경험에서도 당장 생기지 않는다고 이상할 것도 없고 없더라도 화면을 그려내는데 문제가 되지 않았기 때문이다.
 
로컬에서 약 40만 건의 큐를 발행하는 데는 약 4시간 정도가 필요했고 DB 커넥션 타임아웃 문제로 30분마다 종료하고 다시 실행하는 것을 반복해야만 했다.
메시지가 적재 되고 이를 소비하는 속도는 아주 빨라서 큐를 발행하는 동시에 빠르게 소비되었기 때문에 큰 문제없이 작업을 마칠 수 있었다.
 


하지만 역시나 인생은 실전이다. 새벽 배포를 마치고 대응을 위해 바로 당일 출근을 했는데 문제가 나를 기다리고 있었다.
 
아주 일부의 케이스에서만 발생 할 것이라 예상한 엣지 케이스에 대한 문의가 연이어 인입되었다.
어디서부터 잘못된 걸까? 놓친 부분이 있었나? 롤백은 절대 안 돼!!
 
그렇게 찾아낸 결과 문제는 데이터 그 자체였다.
같은 값들을 묶어주면 될 거라 생각했고 그 연결이 제대로 되었는지만 확인했지만, 애초에 사용자가 우리의 서비스에 정확한 값을 넣어 제대로 사용하고 있을 것이란 가정이 틀렸던 것이다.
 
정말 심한 경우를 예를 들자면 "서울특별시 성동구"까지만 입력한 주소에 3채 이상의 건물을 등록해서 관리하는 수준의 회원이 있었다. 이러니 데이터 연동 이후 동일 값 검증은 통과해도 변경된 비즈니스 로직에 전부 걸려버리는 것이었다.
 
이런 경우는 처음 준비했던 버저닝을 했다면 큰 문제없이 넘어갔을지도 모르겠다.
하지만 이런 케이스는 그동안 엉터리 데이터로 엮여 있던 것을 푸는 과정이라 생각하면 나름 나쁘지 않은 결과라고 생각했고, 배포 이후 약 일주일 동안 CX팀으로부터 수많은 고객 요청을 전달받았고, 풀어내고 있다.
그래도 나름 헤비 유저들에게만 발생하는 문제였어서 업무가 마비될 정도는 아니어서 다행이었다.
 


 
이번 작업은 이 서비스가 처음 만들어질 때부터 존재했던 기능에 대한 리팩터링이었다.
2년만 지나도 엄청난 레거시가 될 수 있다는 걸 많이 느꼈다. 하루만 지나도 레거시라는 것이 농담이 아니다.
 
회사에서는 이번 작업을 개발팀의 기술 부채 갚는 시간, 투자라고 했지만 사실 이 작업을 하며 나는 사채를 당겨 은행 빚을 갚는 심정이었기 때문에 굉장히 마음이 불편했다.
 
수많은 아쉬움이 남은 작업이었다.

  • 어디서부터 어디까지 개편해야 이 작업이 끝나는지 알 수 없었기에 정말 잘 고려된 정책이 있었다면
  • 인수 조건이 있었다면
  • 적어도 버저닝 전략을 사용하거나 일부 사용자에게만 오픈하며 확장하는 방식이 가능했더라면
  • 리팩터링에 앞서 기존 동작의 성공을 보장하는 테스트 코드가 없는 부분들이 있었기에 테스트 코드가 있었다면
  • 적어도 테스트 코드를 보완하고 작업을 할 시간이 있었다면
  • 코드를 작성한 개발자가 아니면 어떤 규칙으로 서비스가 동작하는지 아는 동료가 없었기 때문에 잘 정리된 기획서가 있었다면
  • 제품의 정책과 히스토리를 관리해줄 PO가 있었다면
  • 퍼널과 클라이언트에 의존적이지 않은 로직을 작성할 수 있었다면
  • 반복되는 코드를 생산하는 과오를 남기지 않고 역할과 책임을 제대로 분리할 수 있었다면
  • 시간 핑계로 "아는 사람만 아는" 이해할 수 없는 코드를 남기지 않을 수 있었다면

 
정말 많은 게 아쉬웠다. 주어진 환경에서 최선의 선택을 해서 문제를 해결하는 것이라곤 하지만 열악한 환경이란 것은 부정할 수 없었다.
그러다 보니 기술적인 문제보다는 다른 영역에서의 제약 사항을 돌파하는 데 더 많은 노력과 고민을 한 것 같다.
 
요즘의 나는 많은 문제를 해결하고 있지만 그것이 기술적 도전이 아니기에 넓고 얕은 역량의 개발자가 되는 건 아닌가 하는 걱정이 있다.
아무렴 개발자의 역량이 기술 문제뿐이 아니고 시니어가 되려면 여러 다른 역량이 필요한 것은 맞다.
하지만 우리 팀 내에서는 인정받더라도 개발자 시장에서 평가를 받는다고 가정해 볼 때면 "너 고생한 거 알겠는데 그래서 뭐? 우린 그런 고생할 일 없어, 네가 한 경험이 필요하지 않아"라는 말을 들을 것만 같은 막연한 두려움이 있다.
많은 아쉬움이 있지만 그것이 문제가 되지 않을 만큼의 역량이 나에게 없다는 것이 가장 아쉬운 점이었다.
 
약 100일간 진행한 이번 작업을 통해 실제 운영 환경에서의 데이터는 언제나 예측할 수 없다는 것, 스터빙의 함정, 사용자는 우리가 예상한 대로 서비스를 사용하지 않는다는 것, 동작하는 코드와 쿼리보다 이를 검증하는 코드와 쿼리가 몇 배는 더 중요하다는 점을 배웠다.
 
그저 있었던 일을 적고, 느낀 점을 적었을 뿐일 글이지만, 100여 일의 고민, 고통, 열정, 후회를 이 글에 털어내고 다시 앞으로 나아가기 위해 회고를 작성한다.
 

반응형

댓글