잘못된 분석은 무엇으로부터 시작될까
728x90
반응형

이슈가 생겼을 때, 보통은 잘못된 분석이 원인인 경우가 많다. 그렇다면 이 잘못된 분석은 어떤 원인으로 발생할까? 이 의문을 조금이나마 풀어준 경험을 공유해볼까 한다.

 

나에게 "왜 매번 너에게만 이런 일이 생기냐"고 했던 분이 생각난다

 

최초 발견

내가 겪은 이슈는 아래와 같다.

  • 로컬 환경에서 테스트 중 특정 화면의 조회 기능 문제를 발견
    • 승인여부에 Y를 입력하여 조회시 결과가 조회되지 않음 (아래 이미지 참고)
    • 전체 혹은 N을 입력했을 때에는 정상 조회가 됨
  • 개발서버에서 테스트 시 문제가 없음

// 에러 메세지
Caused by: java.lang.IllegalArgumentException: org.hibernate.query.SemanticException: Could not interpret path expression 'user.businessVerifiedAt'

 

 

 

원인 및 해결

결과부터 이야기를 하자면 잘못된 쿼리를 사용하고 있었던 것이 문제였다. 리스트를 가지고오는 API에서 리스트를 조회하는 쿼리Count 쿼리의 조건이 일치하지 않아서 생긴 문제. 리스트 조회용 쿼리에는 Join이 있었지만 Count 쿼리에는 없었다. 문제 분석 과정은 아래와 같다.

  • 디버그 중 Pagination을 처리하는 위치에서 Exception 발생 발견
    • public abstract class PageableExecutionUtils - public static <T> Page<T> getPage
    • getPage 함수는 PageImpl을 return해준다
  • 화면 진입 후 처음 조회 API 호출시
    • Pageable : 조회된 아이템들 중 어디부터 시작해서 가져올것인지를 뜻하는 offset이 0으로 세팅되어 있다.
    • 한 페이지에 들어가는 아이템 갯수조회 결과 갯수를 비교
// getPage 함수
public static <T> Page<T> getPage(List<T> content, Pageable pageable, LongSupplier totalSupplier) {

    // 일부 생략

    // 첫페이지 : pageable.getOffset() == 0
	if (pageable.isUnpaged() || pageable.getOffset() == 0) {

		if (pageable.isUnpaged() || pageable.getPageSize() > content.size()) {
			return new PageImpl<>(content, pageable, content.size());
		}
        // 에러 발생 시점
		return new PageImpl<>(content, pageable, totalSupplier.getAsLong());
	}

	if (content.size() != 0 && pageable.getPageSize() > content.size()) {
		return new PageImpl<>(content, pageable, pageable.getOffset() + content.size());
	}

	return new PageImpl<>(content, pageable, totalSupplier.getAsLong());
}
  • 만약 조회 결과 갯수가 더 적으면 (한 페이지에 들어가는 아이템 갯수 > 조회 결과 갯수)
    • PageImple 인수의 total 자리에는 content.size가 들어감
    • 에러 발생하지 않음
  • 만약 조회 결과 갯수가 같거나 더 크면 (한 페이지에 들어가는 아이템 갯수 <= 조회 결과 갯수)
    • PageImple 인수의 total 자리에는 count 쿼리가 들어감 (⚠️문제의 부분)
    • 앞에서 언급한 Join이 없는 쿼리가 동작
    • Where절에는 Join에 들어가야 할 테이블의 필드가 조건에 들어가 있어서 에러가 발생

 

여기까지가 이슈 발견부터 해결까지의 과정이다. 하지만 이 과정동안 오랜 시간을 소요했다. 잘못된 분석을 여러번 했기 때문. 어떤 잘못을 했고 어떻게하면 이런 잘못을 다시 안할 수 있을지 알아보자.

 

 

 

 

잘못된 분석

QueryDSL 이슈

테스트 과정에서 특정 조건이 Y인 경우에만 에러가 나타났다. 해당 조건을 제거하거나 N인 경우에는 정상 조회가 되었다. 검색조건의 Y와 N의 차이는 아래 함수 차이다.

// Y인 경우
private fun isNotNullBusinessVerifiedAt(): BooleanExpression {
    return user.businessVerifiedAt.isNotNull
}

// N인 경우
private fun isNullBusinessVerifiedAt(): BooleanExpression {
    return user.businessVerifiedAt.isNull
}

isNull인 경우에는 문제가 없었지만 isNotNull에만 문제가 생긴 것. 최초에는 isNotNull 함수쪽에 매몰되어 있었다. 하지만 isNull과 isNotNull은 구조적으로 동일하기에 이부분이 원인이라고 보기 어렵다

 

DB 이슈

로컬환경에서 테스트할 때에는 에러가 발생하지만 개발서버의 경우에는 동일하게 테스트시 문제없이 동작하였다. 로컬환경에 DB만 개발용 DB로 변경해서 테스트했을 때에도 문제가 없었다. 직장 동료로부터 QA 서버에도 동일한 문제가 있고 운영서버에는 괜찮다는 얘기를 들었다. 그래서 아래와 같이 정리할 수 있었다.

  • 정상 : 운영DB, 개발DB-개발용 스키마
  • 비정상 : 로컬DB, 개발DB-QA용 스키마

같은 개발DB에서도 다른 결과가 나오기에 데이터 문제인지 확인하기 시작했다.

 

테스트하던 중 갑자기 에러가 안나타던 순간도 있었다. 😨shxx

특정 데이터 이슈

QA DB로 테스트를 진행했다. Where절 조건에 해당되는 테이블의 필드값을 변경하면서 테스트를 해보았다. 이 과정에서 QA 담당자의 계정 데이터쪽을 변경했을 때에만 에러에 영향을 준다는 것을 파악했다. 앞에서 언급한 테이블의 필드값을 null 혹은 임의의 값을 입력했을 때 에러가 나타나는 것을 발견한 것이다.

  • 특정 데이터의 해당 필드를 임의의 값으로 변경했을 때 조건 Y에서 에러가 나타남
  • 특정 데이터의 해당 필드를 null로 변경했을 때 조건 N에서 에러가 나타남

기존에는 조건 N인 경우에는 에러가 나타나지 않았기 때문에 점점 데이터쪽 문제가 아닐까 하는 생각에 매몰되었다. 일단 QueryDSL과 데이터 사이에서 생기는 이슈라고 밖에 생각이 되지 않아 디버깅을 시작했다. 그리고 디버깅을 통해 본 글의 앞부분에서 언급한 진짜 원인을 찾게 되었다.

 

 

 

왜 잘못 분석하였나

QueryDSL 이슈

위에서 작성했듯 동일한 코드에서 각각의 서버에서 서로 다른 결과를 보여준다. QueryDSL에서 제공하는 함수의 경우에도 동일한 구조에서 다른 결과가 나올 가능성은 매우 적다. (isNull, isNotNull) 그럼에도 이 부분에 너무 시간을 소비하였다.

DB 이슈

이번 이슈의 가장 잘못된 접근 방식이었다. 가장 큰 착각은 운영DB에 문제가 없다고 생각한 것이다. 제대로 원인 파악 후 운영서버쪽에도 문제가 있음을 확인하였다. 로컬DB의 테스트 데이터는 운영DB를 바탕으로 만든 것이기에 문제가 있었던 것이다.

  • 개발 서버쪽은 테스트 데이터가 적다. 이는 한페이지에 모든 데이터를 넣을 수 있다는 뜻이고 동시에 Count 쿼리가 동작하지 않는다는 뜻이다.
  • 내 개인 로컬환경의 테스트 데이터는 운영DB에서 가지고 왔기 때문에 Count 쿼리가 동작했던 것이다. 게다가 운영 서버쪽에 문제가 없다는 것도 동료의 얘기만 듣고 확인을 안했기 때문에 특정 DB의 이슈로 생각했던 것이다.

특정 데이터 이슈

QA 담당자는 본인 소유의 여러 계정으로 수차례 테스트를 진행했다. 특히 몇몇의 계정으로는 여러개의 데이터를 생성했기 때문에 적은 갯수의 계정 데이터만 변경해도 한 페이지에 들어갈 데이터 이상의 갯수가 조회될 수 있다. 데이터가 한 페이지 내 들어갈 수 있는 양을 초과했을 때 count 쿼리가 동작하기에 에러가 나타났던 것이다. 이를 특정 계정 데이터의 이슈로 오해했다.

 

 

 

 

고찰

신뢰할 수 있는 테스트를 진행해야 한다. 아주 예외적인 상황이 생겼다고 생각하는 것은 충분히 분석한 뒤에 해도 된다. 2개 이상의 상황 혹은 환경을 비교하려면 충분히 비슷한 상황 혹은 환경으로 만들어야 한다. 이번 경우는 데이터 분량에서 차이가 났기 때문에 잘못된 분석에 매몰된 것도 있다. 테스트 데이터의 분량도 테스트 신뢰도에 영향을 끼친다.

 

최근 상황을 기준으로 팩트 체크를 해야 한다. 운영서버쪽에 문제가 없다고 착각한 것 역시 문제가 있었다. 운영서버와 개발서버의 개발용 스키마가 정상동작한 것처럼 보였기에 로컬 환경과 QA용 스키마가 예외상황을 만든 것처럼 보인 것. 실제로는 로컬 환경과 QA용 스키마쪽에서 당연한 에러를 보여준 것이며 개발서버는 정상인 것처럼 보였을 뿐이다. 당연히 문제가 없다고 생각만 할 것이 아니라 모든 상황을 최근 시점에서 확인해봐야 한다.

 

 

 

.

728x90
반응형