이번에 Drive-Servie
를 개편하면서 크고 작은 실수들로 다양한 에러를 맞이하고 여러 번 hotfix
를 진행하며 시스템의 안정성을 올리고 테스트
방법에 대해 수많은 고민을 해왔습니다.
갑자기 이런 생각이 들었습니다. 우리에게 있어서 "에러" 라는 것은 어떤 것을 의미하나요?
클라이언트에게 500번
, 400번
상태코드가 내려가는 것만이 에러인가요? 아니면 사용자는 인지하지 못하지만 내부적으로 데이터 정합성
이 맞지 않거나 기타 문제가 생기게 되는 것까지도 에러라고 봐야 할까요?
물론 전부 에러라고 보는 것이 맞지만, 우리가 시스템
을 개선하거나 에러
를 처리할 때 눈에 잘 드러나지 않는 데이터
가 꼬이고 있는 오류라던지 이런 시스템 깊숙이 오래전부터 대대로 내려오는 심각한 논리 오류
를 어떻게 잡아내고 개선할 수 있는지에 대한 고민을 나누고자 합니다.
에러는 항상 우리를 피해 도망가고, 우리는 에러를 잡아 없애려 나날이 추격전
을 벌입니다.
우리는 숨어서 많은 문제를 만드는 잘 보이지 않는 오류를 어떻게 잡을 수 있을까요?
Drive-Service
를 작업하면서 드러난 문제와 숨어서 보이지 않는 문제
를 겪으며 많은 생각을 하게 되었습니다. 드러난 문제란 사용자
에게 바로 보이는 에러를 뜻하고, 보이지 않는 문제는 데이터가 틀어져있으나 응답
에는 전혀 문제가 없이 보이는 문제입니다.
첫 번째 예시
를 들어보겠습니다.
드라이브
에서 특정 폴더
나 파일
을 삭제하고 휴지통에서 해당 폴더나 파일을 완전 삭제하려고 할때 파일과 폴더는 데이터들의 PK
가 휴지통의 테이블의 필드가 되기 때문에 휴지통을 완전삭제 하면서 파일과 폴더도 완전 삭제하게 됩니다.
여기서 휴지통
을 생성할때 폴더
와 파일
의 PK 대신, 드라이브의 PK
를 반대로 넣어서 휴지통에서 폴더와 파일을 완전 삭제할 때 내부 데이터 검증
에 통과하지 못하여 삭제되지 않는 문제가 있었습니다.
이 문제는 왜 생겼을까요?
fun registerWastebasket(driveId: Long, objectId: Long) : Wastebasket
이런 함수가 있다고 해보겠습니다.
두 개의 파라미터
를 받는 이 함수는 파라미터
의 타입이 같기 때문에 서로 위치를 바꾸어 넣어도 전혀 에러가 발생하지 않습니다. 이로 인해 문제
가 발생
하는 것입니다.
또한 이렇게 데이터
를 저장해도 잘못된 파라미터로 SQL이 실행될때 오류
를 발생시키지 않기 때문에 이 데이터는 그대로 저장
되게 되고 사용자가 휴지통
을 완전 삭제하는 동작을 실행시키기 전까지는 전혀 문제를 알 수 없다는 것입니다.
그렇다면 왜 이 문제는 사전에 방지할 수 없었을까요?
테스트 코드
를 작성했으나 Business-Layer
Test는 모두 Mocking 해서 작성했기 때문에 실제 구현체
를 동작시켜 데이터를 저장하고 값을 검증하는 완벽한 검증
을 수행하지 않았기 때문입니다.
데이터 정합성
문제를 발생시키는 이런 오류들은 즉시 오류
가 발생하지 않고 잠재적 문제
를 만들어내는 존재이므로 빠른 시일
내로 파악이 되야 최소한의 수정으로 논리적 오류를 잠재울 수 있습니다.
이 문제는 object_id
가 잘못 저장되어 있는 데이터를 찾아서 원본 파일/폴더 데이터를 찾고 키를 업데이트 시키는 방식으로 잘못된 데이터
를 모두 바로잡았습니다.
또 문제가 되는 데이터를 수정함과 동시에 잘못된 코드를 hotfix
함으로써 이 문제를 해결했습니다.
그렇다면 문제만 해결하면 해피 엔딩
일까요?
Drive-Service
는 생각보다 큰 서비스라서 여러가지 의존성으로 인해 Classist
스타일의 테스팅이 굉장히 어렵다는 단점이 있습니다. 정말 필요한 Bean
만 로드해서 가볍게 테스트 할 수 있는 구조를 만들어야 하는데, 다른 서버
에서 데이터를 받아오거나 외부 통신
을 하는 일이 굉장히 많습니다.
이 논리 오류가 생각보다 큰 문제를 야기하는 이유는 데이터
를 저장
할때는 전혀 문제가 되지 않는다는 것입니다. 그 말은 즉슨, 사용자가 파일
이나 폴더
를 영구삭제 하지 않고 휴지통에만 넣어 둔다면 이 문제가 확인
되지 않는다는 뜻이기도 합니다.
따라서, 이 문제는 Business-Layer Test
에서 데이터 저장(변경)이 이루어진 뒤에 다시 DB 조회를 통해 검증했다면 기대값과 실제값이 달라서 테스트가 실패했을 것이므로 애초에 Mocking
하지 않은 테스트를 했다면 문제없이 동작했을 것입니다.
이 일이 발생한 이후부터 저는 잠재적인 위협
을 주는 다양한 논리 오류가 내가 유지보수
하는 서비스에 포함되어 있는지에 대해 사려깊게 생각하고 고민
하기 시작했습니다.
최근 두 가지 케이스 정도의 데이터 정합성 이슈를 발견했습니다.
이 오류를 파악하면서, 코드레벨
에서 문제를 찾기는 어려웠습니다.
또 이 문제가 여러 케이스에서 접근해서 테스트
를 해봤지만 제가 동작시켰을때는 모두 정상이였습니다.
하지만 데이터를 직접 분석하기 시작하니 문제가 보였습니다.
첫 번째 케이스는 문제가 되는 데이터가 매일 250건
이 생겼고, 데이터 업데이트 일시
가 밀리초 단위밖에 차이나지 않았습니다. 그 말은 즉슨, 특정 스케쥴러
가 순차적으로 데이터를 업데이트 하는 중에 문제
를 발생
시켰다는 말이 된다고 판단
했습니다.
그런데 갑자기 궁금해졌습니다.
혹시 이 케이스 말고 반대
의 문제 케이스는 없을까? 그렇게 두 번째 문제 케이스
를 보게 되었습니다.
이 문제를 살펴보다보니 정말 심각한 문제는 다른 곳에 있다는 것을 알게 되었습니다.
첫째로, 첫 번째 케이스의 문제는 정말 단순히 스케쥴러
에서 에러가 발생하여 모니터링 중에 알게된 문제로 그렇게 어려운 문제
가 아니였습니다.
진짜 큰 문제는 두 번째 케이스였는데 이 케이스의 잘못된 데이터
는 약 7만건
이상이며 6년전부터 이미 이 문제는 발생하고 있었지만 사용자
에게 에러
가 발생하지 않아 아무도 몰랐던 것입니다.
7만건
의 데이터는 대부분 파일 데이터
로, S3 Bucket의 물리 삭제가 데이터 불일치 이슈로 인해 6년
동안 지워지지 않고 있었던 것입니다.
파일을 물리 삭제하는 코드는 반복문을 돌면서 여러 트랜잭션
과 결합되어 있기 때문에 실제 파일이 삭제됬지만 파일 삭제 예정 데이터
를 넣는 테이블에는 아직 삭제가 필요한 상태로 남아서 불필요한 요청을 S3
에 보낼 가능성도 굉장히 높습니다.
환율도 높아진 시점에 AWS 비용
을 낭비하고 있는 이 문제는 6년
동안 알려지지 않았지만 이번에 시스템을 분석하고 데이터 형태
를 보면서 찾아내게 되었고 현재 팀에 공유하고 이 문제
를 분석 & 해결 중입니다.
다음 글은 이 문제를 해결하고 어떻게 유연하게 문제
를 풀어낼 수 있었는지 작성
하게 될 것 같습니다.
파일
이 비정상적
으로 삭제되지 않고 오랜시간 완전 삭제 상태로 방치
되고 있었다면 모니터링 스케줄러라도 개발
되어 알람
을 줄 수 있었다면 이런 문제
가 생기지 않았을거라 생각합니다.
어떻게 하면 스케줄러
를 확신할 수 있을까? 매일 매일 도는 스케줄러
가 논리 오류를 만들어 내는 존재가 되어버렸다면 정말 상상도 하기 싫을 만큼 끔찍한 결과
를 가져오게 됩니다.
처음부터 제가 맡아서 유지보수
한 애플리케이션
이 아니라면, 다양한 가설
을 세우고 많은 검증
을 통해 문제
가 있거나 개선
할 수 있는 부분을 늘 고민해야 한다는 것을 느꼈습니다.
이번 애플리케이션 아키텍처
를 수정하고 구조 개선을 할때 논리적인 오류 발생을 최소화하기 위해 구조
를 개선
하고 함수를 분리하는 등의 작업을 주 업무로하고 현재 애플리케이션
의 코드는 무결하게 동작한다고 믿고 업무
를 수행
했습니다.
지금 이 업무를 돌아본다면, 가장 잘못된 결정
중에 하나가 아니었을까 생각합니다.
다음에 구조를 개선하거나 코드 리팩토링
등의 업무를 해야한다면 현재 시스템에 대해서도 무결성 검증
을 진행하며 점진적 리팩토링 방식을 택하여 조금씩 시스템
을 개선해 나갈 것입니다.
내가 만들어가는 프로덕트
를 사랑하고 그 마음 하나로 조금만 관심을 가지고 꼼꼼히 모니터링
을 한다면 충분히 개선과 오류 포착 및 개선등을 할 수 있다는 것을 배우게 되었고 앞으로도 우리 프로덕트를 사랑하고 진심으로 돌보고 관심가져 관찰
해야겠다는 마음을 가지게 되었습니다.
늘 숨어있는 에러를 잡아 헤매는 많은 개발자 분들 언제나 화이팅입니다!
에러
와 장애
를 박멸하는 그날까지 매일 고민하고 머리를 싸매는 사람이 되겠습니다 😎