본문 바로가기
에러 핸들링

ArithmeticException 해결하기

by kdohyeon (김대니) 2023. 2. 12.
반응형

들어가며

운영하는 하나의 서비스에서 에러가 발생하여 슬랙 알럿이 왔다. 왜 발생했는지 원인을 알아보고 해결해보도록 하자.

알럿 발생

현상

보통 이런 에러가 발생하면 가장 먼저 로그 시스템에 접속하여 어떤 에러 메시지가 남겨졌는지 확인한다. 에러 메시지를 확인해보니 다음과 같았다 (에러 메시지에 포함된 회사 정보는 살짝 변경했다.)

NullPointerException

[CONTROLLER-EXECUTION] class=sampleController, method=GET, path=/api/v1/sample, statusCode=500, exceptionMessage=null, executionTimeMills=44

exceptionMessage=null 인것으로 보아 해당 API 가 호출될 때 NPE (Null Pointer Exception) 이 발생하는 것으로 추정했다.

ArithmeticException

로그로만은 어떤 에러인지 확인하기가 어려워 모니터링 툴로 활용 중인 PinPoint 를 확인해보았다. PinPoint 에서는 조금 더 자세하게 어떤 호출이 발생하는지, 어떤 에러가 발생했는지 기록이 남아서 확인하기가 좋았다.

ArithmeticException 발생

ArithmeticException 이란?

나누기 0 을 하면 발생하는 에러라고 보면 된다.

Java Arithmetic Exception is a kind of unchecked error or unusual outcome of code that is thrown when wrong arithmetic or mathematical operation occurs in code at run time. A runtime problem, also known as an exception, occurs when the denominator is integer 0, the JVM is unable to evaluate the result, and therefore the execution of the program is terminated, and an exception is raised.
https://www.educba.com/java-arithmeticexception/ 

왜 ArithmeticException 이 발생하는가?

내부 로직 중에 아래와 같은 코드가 포함되어 있다.

private BigDecimal calculateTotalRate(long count, long sum) {
    return BigDecimal.valueOf(sum).divide(BigDecimal.valueOf(count), 1, RoundingMode.HALF_UP);
}

ArithmeticException 이 발생하려면 count 가 0 이 되어야 하는데, 해결책은 간단하게 count 값이 0 인 경우에 대해서 방어 코드를 추가해주면 된다. 하지만 왜 count 가 0 이 발생하는 경우가 어떤 경우인지 좀 더 파악할 필요가 있다.

count 는 왜 0 이 될 수 있는가?

calculateTotalRate 메소드는 리뷰(review)의 평균 점수를 구하는 메소드이다. count 와 sum 은 리뷰 통계 테이블에서 조회한 데이터이며 count 는 계산하고자 하는 리뷰의 개수를 의미하며, sum 은 총 리뷰가 받은 점수 (1-5 scale) 를 의미한다. 리뷰가 생성되면 새로운 row 가 생기고, 리뷰가 삭제되면 sum 과 count 는 그에 따라 값이 줄어든다.

에를 들어, 상품 ID 100 에 대해 5점짜리 리뷰가 달렸다. 리뷰 테이블에는 ID 1,001 으로 새로운 리뷰가 생성되었고, 리뷰 통계 테이블에는 리뷰 1,001 에 대해 count=1, sum=5 의 리뷰 통계 row 가 생성되었다. 이후 리뷰 ID 1,001은 삭제되었고, 리뷰 통계 테이블에서도 알맞게 count=0, sum=0 으로 변경되었다. 

평균 점수를 구하는 로직에 ArithmeticException 이 발생하지 않으려면 리뷰 통계 테이블에 count=0 이 되면 row 를 삭제하거나 로직 자체에 방어 로직이 있어야 하는데, 그렇게 되지 못했다. row 를 삭제하는 것은 (hard-delete) 실무에서 잘 사용하지 않는 방법이다보니 valid 또는 is_used 등 flag 컬럼을 추가해서 운영할 수도 있지만 그렇게 구현된 상황은 아니었다.

마무리하며

방어로직을 추가하며 더 이상 해당 에러가 발생하지 않도록 수정했다.

if (count <= 0) {
    return BigDecimal.ZERO;
}

해당 이슈를 해결한지 시간이 좀 지났는데, 지금와서 생각해보면 repository 레벨에서 count 가 0 인 경우에 대해 방어 로직을 추가해두는 것이 좀 더 좋았을 것 같다는 생각이 든다.

그리고 이 에러는 2022년 4월 중순에 고쳤는데, 2월부터 발생을 했었다. 에러가 발생하는 빈도가 적어서 슬랙 알럿이 안왔던 것 같은데, 좀 더 빠르게 캐치할 수 있도록 에러로그를 분석하여 평소에 발생하지 않는 에러 메시지에 대해서는 리포팅을 해줄 수 있는 시스템을 만들어 두면 좋겠다고 생각했다. (언제 만들어볼까..)

반응형

댓글