ABAP SQL 튜닝 ⑤ - OFFSET vs 키셋 페이징

ABAP SQL 튜닝 5편 배너

ABAP SQL 튜닝 시리즈 다섯 번째 글입니다. 이번 편과 ⑥편대량 조회를 다룹니다. 이번은 조회 속도, 다음은 메모리입니다.

페이징이 필요한 순간

수백만 건짜리 테이블을 화면에 뿌릴 수는 없습니다. 한 번에 일부만 잘라서 가져오는 것이 페이징입니다.

  • Fiori 목록 화면 — 스크롤할 때마다 다음 묶음
  • 대량 리포트 — 페이지 단위 조회
  • 배치 처리 — 구간을 나눠 순차 처리

방식은 크게 두 가지이고, 성격이 완전히 다릅니다.

OFFSET 기반 페이징

가장 직관적인 방식입니다. "처음 100건 건너뛰고 그다음 20건"처럼 지정합니다.

ABAP SQL에서는 OFFSET을 쓸 수 있습니다.

1DATA(lv_size)   = 20.
2DATA(lv_offset) = 100.
3
4SELECT vbeln, posnr, netwr
5  FROM zsales
6  WHERE werks = @lv_werks
7  ORDER BY vbeln, posnr
8  INTO TABLE @DATA(lt_page)
9  UP TO @lv_size ROWS
10  OFFSET @lv_offset.
INFO

OFFSET은 ABAP 릴리스 7.50 이상에서 사용할 수 있고, ORDER BY가 반드시 있어야 합니다. 정렬 없이 건너뛰는 것은 의미가 없기 때문입니다. 문법 순서와 사용 가능 여부는 실제 시스템에서 확인하세요.

왜 뒤로 갈수록 느려지나

여기가 핵심입니다. OFFSET 100000은 "10만 건을 안 읽는다"는 뜻이 아닙니다.

!실제 동작건너뛸 행도 일단 만들어낸다

DB는 조건에 맞는 행을 정렬해서 앞에서부터 세어 나갑니다. 10만 번째부터 주려면 앞의 10만 건을 만들어 버려야 합니다.

결과비용이 페이지 번호에 비례

1페이지는 빠르고, 5,000페이지는 느립니다. 이걸 deep paging 문제라고 부릅니다.

10건을 받는데 앞의 10만 건을 만들어 버리는 셈이니, 페이지가 깊어질수록 낭비가 커집니다.

데이터가 바뀌면 페이지가 어긋난다

또 하나의 문제입니다. 1페이지를 보고 2페이지로 넘어가는 사이에 누군가 데이터를 추가하거나 지우면 경계가 밀립니다.

  • 앞쪽에 1건이 추가되면 → 1페이지 마지막 행이 2페이지에 다시 나타남
  • 앞쪽에서 1건이 삭제되면 → 1건이 건너뛰어져 안 보임

조회 전용 화면이면 넘어갈 수 있지만, 순차 배치 처리에서 이런 누락이 생기면 사고입니다.

키셋 기반 페이징

다른 접근입니다. "몇 건 건너뛸지"가 아니라 "어디서부터 이어서 읽을지" 를 지정합니다. 직전 페이지의 마지막 행 키를 조건으로 넘깁니다.

1" 1페이지 - 처음부터
2SELECT vbeln, posnr, netwr
3  FROM zsales
4  WHERE werks = @lv_werks
5  ORDER BY vbeln, posnr
6  INTO TABLE @DATA(lt_page)
7  UP TO @lv_size ROWS.
8
9" 다음 페이지 - 마지막 행의 키보다 큰 것부터
10DATA(ls_last) = lt_page[ lines( lt_page ) ].
11
12SELECT vbeln, posnr, netwr
13  FROM zsales
14  WHERE werks = @lv_werks
15    AND ( vbeln > @ls_last-vbeln
16       OR ( vbeln = @ls_last-vbeln AND posnr > @ls_last-posnr ) )
17  ORDER BY vbeln, posnr
18  INTO TABLE @DATA(lt_next)
19  UP TO @lv_size ROWS.

왜 비용이 일정한가

조건이 vbeln > '0000012345' 형태의 범위 조건으로 바뀝니다. 정렬 키에 인덱스가 있다면, DB는 인덱스에서 그 지점을 바로 찾아 거기서부터 20건만 읽으면 됩니다.

건너뛸 행을 만들 필요가 없습니다. 그래서 1페이지든 5,000페이지든 비용이 거의 같습니다.

복합 키 처리가 관건

정렬 키가 유일하지 않으면 문제가 생깁니다. vbeln만으로 정렬했는데 같은 vbeln이 여러 건이면, 경계에서 행이 잘리거나 겹칩니다.

그래서 위 예제처럼 정렬 키를 유일해질 때까지 늘리고, 조건도 그에 맞춰 씁니다.

1" (vbeln, posnr) 순서로 "마지막 행보다 뒤"를 표현
2AND ( vbeln > @ls_last-vbeln
3   OR ( vbeln = @ls_last-vbeln AND posnr > @ls_last-posnr ) )

키가 3개면 조건이 한 단계 더 중첩됩니다. 다소 번거롭지만 이 조건이 정확해야 누락·중복이 안 생깁니다.

TIP

정렬 키에 유일성을 보장하는 필드를 마지막에 하나 넣어두면, 조건이 단순해집니다. 대개 기본키의 마지막 필드를 tie-break로 씁니다.

언제 무엇을 쓰나

OFFSET임의 페이지 점프가 필요할 때
  • "7페이지로 바로 이동" 같은 UI
  • 전체 페이지 수가 적을 때
  • 구현이 단순하다
  • 깊은 페이지에서 느려진다
키셋순차적으로 훑을 때
  • 무한 스크롤·"더 보기"
  • 배치의 순차 구간 처리
  • 깊이와 무관하게 일정한 비용
  • 임의 페이지 점프가 불가

정리하면 페이지 번호를 눌러 이동하는 화면은 OFFSET, 아래로 계속 내려가는 화면과 배치는 키셋입니다.

실무에서는 앞쪽 몇 페이지는 OFFSET으로 충분하고, 깊은 페이지가 실제로 발생하는지를 먼저 확인하는 게 현실적입니다. 사용자가 3페이지 이상 안 넘긴다면 굳이 키셋으로 갈 필요가 없습니다.

S/4HANA에서 유의할 점

  • 정렬 비용을 의식하세요. 두 방식 모두 ORDER BY가 전제입니다. 정렬 키에 인덱스가 없고 대상 건수가 많으면, 페이징 방식과 무관하게 정렬 자체가 병목이 됩니다.
  • CDS View + OData로 노출할 때 — Fiori 목록의 $top/$skip은 개념적으로 OFFSET 방식입니다. 백엔드가 대량 테이블이면 deep paging 문제가 그대로 나타날 수 있습니다.
  • 정렬 조건을 화면에서 바꿀 수 있게 만들 때 주의하세요. 사용자가 임의 컬럼으로 정렬하면 인덱스를 못 타는 조합이 생깁니다.

정리

  • OFFSET건너뛸 행도 DB가 일단 만들어냅니다. 깊은 페이지일수록 느려집니다.
  • 그리고 데이터가 바뀌면 경계가 어긋납니다. 순차 처리에는 위험합니다.
  • 키셋 페이징은 마지막 키를 이어받아 범위 조건으로 바꿉니다. 비용이 일정합니다.
  • 대신 복합 키 조건을 정확히 써야 하고, 임의 페이지 점프는 못 합니다.
  • 어느 쪽이든 정렬 키에 인덱스가 있어야 의미가 있습니다.

다음 ⑥편에서는 같은 대량 조회를 메모리 관점에서 봅니다. 페이징과 비슷해 보이지만 목적이 다른 PACKAGE SIZE와 커서 이야기입니다.

Disclaimer — 이 포스트는 AI(Claude)를 활용하여 작성된 초안을 바탕으로 검수 및 보완하여 작성되었습니다. 내용 중 오류나 오타가 있다면 댓글로 알려주시면 감사하겠습니다.