pandaterry's 개발로그

[면접파헤치기] 억단위 데이터 조회시 로딩시간 개선문제 본문

개발/면접대비

[면접파헤치기] 억단위 데이터 조회시 로딩시간 개선문제

pandaterry 2025. 2. 23. 14:06

Q. 이커머스 사이트에서 '나의 구매목록'을 조회하는 기능이 있다고 할 때, 초반에는 데이터가 적어 페이지 로딩이 빠르지만 시간이 지나 억 단위의 데이터가 생성되었다면 조회할 때마다 페이지 로딩이 느려질 것입니다. 로딩 시간을 개선하기 위해 어떤 해결책을 제시할 수 있을까요?

 

이 문제를 봤을 때, pagination을 하면 되는거 아니야? 라고 단순하게 생각할 수도 있다.


그러나 억단위의 데이터의 경우, order by와 limit 쿼리가 실행될 때 페이지가 넘어갈수록 스캔해야할 데이터가 너무 많아지는 문제가 여전히 존재한다.

 

그러면 시간순으로 미리 index 처리를 하면 되지 않는가? 라고 반박할 수도 있다. 그러나 '나의 구매목록' 조회라는 페이지의 특성상 userId로 where 쿼리가 추가적으로 실행되기 때문에 여전히 정렬 인덱스만으로는 문제 해결이 어렵다. 핵심은 인덱스를 아무리 걸어도 스캔하는 수에는 변함이 없다.


다음 예시를 봐보자.

 

스키마 설계

 

다음과 같이 구매목록(purchases) 테이블이 있다고 쳐보자.

 

Column Type Key Description
id BIGINT PK Auto increment
user_id BIGINT - 사용자 ID
product_id BIGINT - 상품 ID
quantity INT - 수량
price DECIMAL(10,2) - 가격
created_at TIMESTAMP - 생성일시

 

Index: (user_id, created_at) - 사용자별 구매내역 조회용 복합 인덱스

 

 

 

 

[오답] 인덱스 설정 및 쿼리 최적화의 한계

  1. 첫페이지 요청(최신 10건) 
  2. SELECT * FROM purchases WHERE user_id = 123 ORDER BY created_at DESC LIMIT 10;
  • 인덱스(user_id, created_at)를 타고 바로 찾아감
  • 스캔: 10건

 

  1. 5,000번째 페이지 요청(49,990~50,000번째)
    SELECT * FROM purchases 
    WHERE user_id = 123
    ORDER BY created_at DESC
    OFFSET 49990 LIMIT 10;
  • user_id=123 조건으로 인덱스를 타더라도
  • 49,990번째 레코드를 찾기 위해 앞의 49,990건을 모두 스캔해야 함
  • 스캔: 50,000건

 

  1. 마지막 페이지 요청 (99,990~100,000번째)
    SELECT * FROM purchases 
    WHERE user_id = 123
    ORDER BY created_at DESC
    OFFSET 99990 LIMIT 10;
  • 99,990번째 레코드를 찾기 위해 앞의 99,990건을 모두 스캔
  • 스캔: 100,000건

 

결론

인덱스가 있어도, 페이지 번호가 뒤로 갈수록 스캔해야하는 레코드 수가 선형적으로 증가한다.
사용자가 뒤쪽 페이지를 요청할수록 쿼리 성능이 급격히 저하된다.
이는 인덱스나 쿼리 최적화만으로 해결할 수 없는 구조적인 문제가 여전히 존재한다는 것을 보여준다.

 

 

[정답] 기간별 데이터 파티셔닝(Range Paritioning)

연도별이나 월 등의 기간별 데이터 파티셔닝을 통해 해결해야한다.
쿠팡의 주문목록 페이지를 한번 봐보겠다.

 

이 페이지를 보면 주문목록이 연도별로 정리되어있는걸 알 수 있다.

 

검색을 할 때에서 2개의 연도를 걸쳐서 검색이 안되는 구조인 것으로 보아 연도별 데이터 파티셔닝이 되어있을 것이라 추측할 수 있다.

 

일단 기간별 데이터 파티셔닝이 정답에 가까운 이유는 본질적인 해결이기 때문이다. 주문목록은 원래 기간별로 정렬이 되어있는 구조이며 과거일수록 굳이 조회할 이유가 없는 데이터이기도 하다. 그리고 기간별로 데이터를 파티셔닝을 하면 기간이 짧으면 짧을수록 데이터가 한곳에 집약되는 문제를 해결할 수가 있다.

 

예를 들어, 5년간의 모든 주문목록이 억단위 데이터라면, 연도별로 파티셔닝을 하더라도 연도별로 2000만건으로 스캔해야하는 최대 개수가 줄어버린다.

 

CREATE TABLE purchases (
    purchase_id INT PRIMARY KEY,
    user_id INT,
    product_id INT,
    purchase_date DATE,
    amount DECIMAL(10, 2)
)
PARTITION BY RANGE (YEAR(purchase_date)) (
    PARTITION p2020 VALUES LESS THAN (2021),  -- 2020년 데이터
    PARTITION p2021 VALUES LESS THAN (2022),  -- 2021년 데이터
    PARTITION p2022 VALUES LESS THAN (2023),  -- 2022년 데이터
    PARTITION p2023 VALUES LESS THAN (2024)   -- 2023년 데이터
);