2023년 4월 11일 서비스 장애에 대한 기록 글입니다.
현재 회사에서는 Error 레벨의 로그에 대해서는 슬랙 채널을 통해 알림을 받는 구조로 되어있습니다. 오전 10시 23분쯤 에러로그 채널에서 무한 메세지가 오기 시작했고 대부분 NullPointerException 이었습니다.
보통 에러로그에 찍힌 메세지를 보면 어떤 코드의 몇번째 줄에서 에러가 나고 있는지 파악할 수 있는데 특정 기능에 대한 에러로그가 아니라 주요 기능의 대부분에서 비슷한 에러가 발생하고 있었고 이를 통해 DB가 문제라는 것을 어느 정도 유추할 수 있었습니다.
문제의 원인은 mongoDB의 $out이라는 키워드 때문이었습니다. 팀원분이 운영 DB의 데이터들에 대해 통계성 데이터를 추출해달라는 요구를 받으셨고 이 과정에서 평소처럼 aggregate를 사용하다가 결과를 내보내려고 $out를 사용했는데 $out을 기존에 존재하는 콜렉션에 하게되었고 집계 쿼리의 결과가 서비스의 핵심 컬렉션에 덮어씌어지는 현상이 발생했습니다.
공식문서 : https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/#-out--aggregation-
공식문서에서도 warning이라고 경고를 주고 있습니다. 내용을 살펴보면 기존에 존재하는 컬렉션에 사용하면 데이터를 치환한다는 것입니다.
실제 사용했던 쿼리의 예시는 아래와 같습니다.
MongoDB
body
{
"aggregate":"A",
"pipeline":{
"0":{
"$lookup":{
"from":"B",
"localField":"B",
"foreignField":"_id",
"as":"field"
}
},
"1":{
"$out":"B"
}
}
}
그리고 아래의 결과물이 B라는 컬렉션에 덮어씌어진 것입니다.
다행히 유저 데이터는 아니었고 매일 스냅샷을 생성하고 있었기 때문에 롤백만 하면되서 복구과정이 어렵진 않았습니다.
현재 회사에서 정책적으로 운영 DB에서는 애초에 insert, update, delete를 직접적인 쿼리를 통해 할 수 없게 되어있었는데 저런 집계 쿼리를 사용하다가 저런 결과를 노출하니 굉장히 위험한 키워드인듯합니다.