Jpa query method에서 페이징이 이상하게 동작해요

김재연·2024년 12월 2일
0

배경

회사 프로젝트에서 페이징 API 호출하는게 있는데, 특정 시간에 데이터가 누락된게 확인되었다. 특정 시간동안 DB에 쌓인 건수랑 API 응답에서 size로 준 건수를 비교해봤는데 똑같아서 원인을 알 수 없었다.

누락된 데이터에 대해 몰라 db에 register_ts도 확인해보고, 로그도 확인해봤는데 모두 문제가 없었다.

최후의 수단으로 DB에 있는 데이터와 API 응답으로 온 데이터를 건건이 비교해보았다.

원인

원인은 jpa query method를 통해 페이징 응답을 받고 있는데, 정렬기준이 없어서 데이터가 중복으로 나왔다. 응답 로그를 확인해보니 특정 데이터가 0페이지에서도 나왔고, 8페이지에서도 나왔다.

문제됬던 코드

회사에서 동작했던 코드도 아래 코드와 유사하게 동작했다. 핵심은 JPA query method를 이용하여 findByRequestDateTimeBetween 메서드를 정의했고, PageRequest.of(0, 10) 를 이용하여 Pageable 을 넘겼다. 런타임에서도 잘 동작해서 정상적으로 동작하는줄 알았다. 하지만 쿼리를 살펴보니 order by 조건이 없었고, DB에서는 조건에 해당되고 limit 사이즈에 해당하는 데이터가 있으면 이전에 결과를 반환했던 말던 상관없이 응답을 줬다.

@Entity
@Table(name = "paging_test")
data class PagingTestEntity(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
    val title: String,
    val requestDateTime:  LocalDateTime,
)

@Repository
interface PagingTestRepository : JpaRepository<PagingTestEntity, Long> {
    fun findByRequestDateTimeBetween(startDateTime: LocalDateTime, endDateTime: LocalDateTime, pageable: Pageable) : Page<PagingTestEntity> // 문제됬던 코드
}

@IntegrationTest
internal class PagingTestRepositoryTest(
    private val sut: PagingTestRepository,
) {

    @Test
    fun `saveTest`() {
        val requestDateTime = LocalDateTime.of(2024, 12, 3, 0, 3, 0)
        val title = "saveTest"
        val entity = PagingTestEntity(
            title = title, requestDateTime = requestDateTime,
        )

        sut.save(entity)

         val result = sut.findByRequestDateTimeBetween(
             startDateTime = LocalDateTime.of(2024, 12, 3, 0, 2, 0),
             endDateTime = LocalDateTime.of(2024, 12, 3, 0, 4, 0),
             pageable = PageRequest.of(0, 10),
         ).content
        assertTrue(result.isNotEmpty())
        assertEquals("saveTest", result.first().title)

    }
}

// query
select pte1_0.id,pte1_0.request_date_time,pte1_0.title from paging_test pte1_0 where pte1_0.request_date_time between ? and ? limit ?

image-20241203002601627

원인을 살펴보니 Pageable 객체를 만들때 PageRequest.of 로 만든게 문제였다. 페이징을 위해서라면 정렬조건을 줘야되는데, query method에도 정렬 기준이 없었고, Pageable 에서도 정렬조건이 없으면 데이터베이스 내부 동작원리에 의존하기 때문에 데이터가 중복 및 누락이 발생할 수 있다.

해결

정렬조건을 추가하니 문제는 해결이 되었다. 정렬을 추가하는 방법은 크게 2가지가 있다.

  • 1안, query method에 정렬조건 추가 : findByRequestDateTimeBetweenOrderByRequestDateTime 처럼 orderBy조건을 추가하면된다.
  • 2안, PageRequest.of 에 정렬조건 추가 : PageRequest.of(0, 10, Sort.by("requestDateTime").descending()) 과 같이 정렬 조건 추가

1안과 2안을 고민하다가 1안을 선택하게 되었다.

결론

jpa나 jdbcTemplate을 이용하여 Paging, Cursor 방식을 사용하게 되면 꼭 정렬조건을 넣자. 정렬조건이 없으면 데이터가 DB에서 주고 싶은대로 마음대로 나온다. 꼭 정렬을 주자 !!

느낀점

문제가 됬던 테이블은 1분에 500~600개씩 하루에 대략 70~80만건씩 데이터가 쌓이는 테이블이였다. 우리는 15분단위로 데이터를 조회해가는데, 00:00~00:15 사이에만 간헐적으로 발생해서 원인을 찾기 힘들었다. 간단할줄 알았는데, 생각보다 문제의 원인을 파악하기 위해 많은 시간이 걸렸다. 문제를 해결하는 과정에서 나름대로 가설을 세우면서 문제의 범위를 조금씩 좁히고, 로그 + 디버깅 + 테스트코드 + 데이터를 보면서 원인을 파악하고 해결했을때 개인적으로 많이 뿌듯했다. 앞으로도 이슈가 생겼을때 스스로 부딪치면서 문제를 해결하는 개발자로 성장을 하고 싶다.

소스코드

profile
이제 블로그 좀 쓰자

0개의 댓글