일단 거두절미하고 ES의 Indexing Rate (/s)의 변화부터 보자


인덱싱 rate가 500/s이하에서 10,000/s.. 거의 20배 뻥튀기 성능이 올라갔다.


1. 키바나 모니터링 화면에서 인덱싱 속도 저하 확인
먼저 문제는 RDB -> ES로 전체 색인을 하는 과정에서, ES에 특별한 문제가 없는데도 내가 작업하는 전체 색인만 속도가 갈수록 느려지는 것을 확인했다.
2. Spring log 확인
스프링에서 띄워주는 sql 로그를 확인해보니, limit, offset을 잘 지켜서 가지고 오고 있는데..2,000만개를 기점으로 1000개씩 읽어오는데만 약 1분이 넘게 걸렸다.

어..?
처음 몇 번 돌렸을때는 느려지는게 ES에 너무 많이 데이터를 넣어서인가..? 라고 생각했는데, 진짜 성능저하 원인은 RDB에서 limit, offset으로 읽어오는 과정에 있음을 직감했다
3. 검증
DataGrip으로 offset을 크게 두고 비교해보니, offset=0 보다 offset=1000000...큰 값일때 느려지는 걸 확인했다.
<select id=".." resultMap="..">
select *
from ..
limit #{limit} offset #{offset}
</select>
(sql문은 대충 이런 구조)
OFFSET 데이터베이스 내부 처리:
offset은 데이터베이스에서 쿼리의 처음 N개 결과를 건너뛰는것 처럼 보인다. 하지만 데이터베이스는 모든 행을 디스크로부터 읽어오고, 불필요한 행들을 무시한뒤 결과를 넘기게 되는 식으로 진행된다.큰 OFFSET 값을 사용하면 많은 양의 데이터가 디스크에서 읽혀 메모리로 로드되어야 합니다. 이로 인해 디스크 I/O가 증가하고, 메모리 사용량이 증가할 수 있습니다.
아하😅..
그럼 offset말고 다른 방법은 없나? 해서 찾아보니, Keyset Pagination (또는 커서 기반 페이지네이션)이라는 게 있었다.
Keyset Pagination은 이전 페이지의 마지막 키(마지막에 조회한 row id)를 기준으로 그 다음 키부터 데이터를 가져오기 때문에, 불필요한 행을 건너뛰는 작업이 없습니다.
Keyset Pagination은 항상 인덱스를 활용하여 데이터를 가져오기 때문에, 페이지 번호가 아무리 커져도 성능 저하가 발생하지 않는다. 때문에 데이터가 어 어떤 페이지에 위치해있던지 항상 가져오는데 같은 시간을 유지할 수 있다.
인덱스를 만들거나, 데이터베이스를 파티셔닝 하는 것보다 훨씬 간단하고 직관적인 해결 방법이었다.
sql을 고치면 이렇게 된다.
select *
from ___
where ..
and product_wish.product_seq > #{lastProductSeq}
order by id
limit #{limit}
(처음에는 조회를 하지 않아 lastProductSeq가 없기 때문에,
-1L이나 null을 넣어주고 체크하는 방식을 사용하면 된다)
Keyset Pagination을 사용하면 OFFSET 기반 페이지네이션의 성능 문제를 피할 수 있다.
페이지 이동이나 복잡한 정렬이 아니라면, 대량의 데이터에 대해 페이지네이션을 수행할 때는 Keyset Pagination을 사용하는 것이 성능 측면에서 훨씬 유리하다.
고민 해결!
아직도 말하는 감자지만.. 혼자 문제를 인지하고 해결하다니 괜히 많이 뿌듯~~