모바일 개발의 페이지네이션 — 정의, 유형 및 작동 원리

저자: IT Sectr 게시일: 2026-03-11 읽는 시간: 9 분

페이지네이션은 모바일 앱과 웹 서비스에서 대량의 레코드를 처리하기 위해 데이터를 페이지 단위로 로드하는 기술입니다. Android Developers Documentation (2025)에 따르면, 올바른 페이지네이션 구현은 API 부하를 줄이고, 트래픽을 절약하며, 사용자 경험을 향상시킵니다. 페이지별 로딩을 통해 앱은 모든 데이터가 한 번에 로드될 때까지 기다리지 않고 콘텐츠를 점진적으로 표시할 수 있습니다.

핵심 요점

  • 페이지네이션 — 성능과 트래픽 최적화를 위해 대규모 데이터셋을 청크 단위로 로드하는 방법.
  • Offset 페이지네이션은 오프셋(page/offset)을 사용하여 탐색 — 간단하지만 빈번한 삽입에 불안정.
  • Cursor 페이지네이션은 마지막 레코드의 고유 커서를 사용 — 요청 간 데이터 변경 시 안정적.
  • Keyset 페이지네이션은 고유 인덱스가 있는 컬럼으로 필터링 — 중복 없는 대규모 테이블에 효율적.
  • Time-based 페이지네이션은 타임스탬프별로 레코드 그룹화 — 뉴스 피드와 소셜 네트워크에 편리.

페이지네이션이란?

페이지네이션(라틴어 pagination — 페이지 분할)은 대규모 데이터셋을 순차적 부분(페이지)으로 나누는 기술입니다. 모바일 앱에서는 메시지 목록, 뉴스 피드, 상품 카탈로그, 주문 내역 등 잠재적으로 무제한의 레코드를 포함하는 컬렉션을 로드할 때 페이지네이션이 사용됩니다.

페이지네이션이 없으면 앱은 모든 데이터를 한 번에 로드해야 하므로, 긴 대기 시간, 높은 트래픽 소비, 저사양 기기에서의 불안정한 성능이 발생합니다. 페이지네이션이 포함된 API 요청은 데이터의 한 부분과 다음 부분을 로드하기 위한 메타 정보만 반환하므로, 앱이 수신하는 정보량을 제어할 수 있습니다.

페이지네이션의 주요 지표는 페이지 크기 — 페이지당 레코드 수(보통 10~50), 그리고 페이지 번호 또는 커서 — 데이터셋 내 현재 위치를 가리키는 포인터입니다. 페이지 크기 선택은 데이터 유형에 따라 달라집니다. 컴팩트 요소(이름)는 20~30이면 충분하고, 이미지가 포함된 카드는 10~15가 적당합니다.

모바일 앱에 페이지네이션이 필요한 이유

모바일 기기는 RAM, 프로세서 속도, 트래픽 제한 등 제한된 리소스를 가지고 있습니다. 페이지네이션은 세 가지 주요 과제를 해결합니다: 메모리 소비 감소(보이는 요소만 메모리에 저장), 첫 표시 속도 향상(첫 번째 청크가 전체 세트보다 빠르게 로드), 트래픽 절약(사용자가 목록을 스크롤할 때만 데이터 로드).

페이지네이션의 주요 유형

페이지네이션에는 네 가지 주요 유형이 있으며, 각각 특정 작업을 해결합니다. 방식 선택은 데이터 일관성 요구사항, API 아키텍처, 스토리지 유형, 클라이언트 및 서버 측 구현의 허용 가능한 복잡성에 따라 달라집니다.

유형작동 원리안정성대규모 데이터 속도
OffsetSQL의 LIMIT + OFFSET낮음OFFSET 증가에 따라 저하
CursorWHERE id > last_id높음안정적(O(log n))
KeysetWHERE key > last_key높음안정적(O(log n))
Time-basedWHERE created_at < last_time중간인덱스로 안정적

각 유형을 사용해야 할 때

Offset 페이지네이션은 간단한 구현이 중요한 정적 또는 드물게 업데이트되는 데이터셋에 적합합니다. Cursor와 Keyset은 빈번한 삽입이 있는 동적 데이터에 적합합니다. Time-based는 레코드가 생성 시간순으로 정렬된 시간순 피드에 적합합니다. GraphQL 표준 Relay는 유일하게 권장되는 방식으로 커서 페이지네이션을 사용합니다.

Offset 페이지네이션: 장점과 단점

Offset 페이지네이션은 페이지 기반 로딩의 가장 간단한 유형입니다. 클라이언트는 page와 limit(또는 offset과 limit) 매개변수를 보내고, 서버는 SQL OFFSET과 LIMIT을 적용합니다. 예를 들어, page=2, limit=20은 21~40번째 레코드를 반환합니다. 이 방식은 직관적이며 모든 스택에서 쉽게 구현할 수 있습니다.

python
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/items")
async def get_items(
    page: int = Query(default=1, ge=1),
    limit: int = Query(default=20, le=100)
):
    offset = (page - 1) * limit
    items = await fetch_items(offset, limit)
    total = await count_items()
    return {
        "items": items,
        "total": total,
        "page": page,
        "pages": (total + limit - 1) // limit
    }

데이터 불일치 문제

Offset 페이지네이션의 주요 단점은 레코드 누락 및 중복 문제입니다. 두 요청 사이에 테이블에 새 레코드가 추가되면 OFFSET이 밀려나 사용자가 동일한 레코드를 두 번 보거나 새 레코드를 놓칠 수 있습니다. 이는 일관성이 중요한 뉴스 피드와 채팅에서 심각한 문제입니다.

또 다른 문제는 큰 OFFSET 값에서의 성능 저하입니다. 데이터베이스는 결과를 반환하기 전에 처음 offset 레코드를 스캔하고 건너뛰어야 합니다. offset=100000에서 LIMIT 20이더라도 서버는 스캔에 상당한 시간을 소비합니다. PostgreSQL과 MySQL은 OFFSET이 증가함에 따라 속도가 선형적으로 저하됩니다.

Offset이 여전히 좋은 경우

Offset 페이지네이션은 다음과 같은 경우에 최선의 선택입니다: 관리 패널(데이터 변경이 드물고 페이지 탐색 필요), 리포트 및 히스토리 로그(데이터의 고정 스냅샷), 필터가 있는 카탈로그(특정 페이지로 이동 가능). Offset은 클라이언트 측에서도 가장 구현이 쉬우며, RecyclerView와 Paging 3에서 기본으로 지원됩니다.

Keyset 및 Time-based 페이지네이션

Keyset 페이지네이션은 고유 키(보통 기본 키)를 사용하여 레코드를 필터링합니다. OFFSET 대신 WHERE id > last_seen_id 쿼리를 사용합니다. 이는 레코드 수에 관계없이 안정적인 성능을 보장하며, 새 레코드는 항상 더 큰 id를 가지므로 삽입 시 중복이 발생하지 않습니다.

sql
-- Offset 페이지네이션 (문제 있음)
SELECT * FROM posts
ORDER BY id
LIMIT 20 OFFSET 100;

-- Keyset 페이지네이션 (안정적)
SELECT * FROM posts
WHERE id > 100
ORDER BY id
LIMIT 20;

Time-based 페이지네이션

Time-based 페이지네이션(또는 시간별 커서)은 탐색에 created_at 타임스탬프를 사용합니다. 클라이언트는 마지막으로 로드된 레코드의 타임스탬프를 보내고, 서버는 해당 타임스탬프 이전이나 이후에 생성된 레코드를 반환합니다. 이 방식은 레코드 순서가 게시 시간에 의해 결정되는 소셜 네트워크와 뉴스 피드에서 인기가 있습니다.

Time-based 페이지네이션의 특징은 두 레코드가 동일한 밀리초에 생성된 경우 중복이 발생할 수 있다는 점입니다. 이 문제를 해결하려면 고유 id와 시간 기반 키를 결합합니다: WHERE (created_at, id) < (last_time, last_id). 이러한 복합 커서는 각 레코드의 고유성과 정확한 순서를 보장합니다.

Keyset과 Time-based 비교

Keyset 페이지네이션에는 고유하고 단조 증가하는 값(자동 증가 id, UUID v7)을 가진 컬럼이 필요합니다. Time-based는 created_at이 있는 모든 테이블에 적합하지만 추가적인 중복 처리가 필요합니다. 주요 차이점: Keyset은 모든 삽입 작업에서 안정적으로 작동하는 반면, Time-based는 동일한 타임스탬프에 영향을 받습니다.

프로젝트에 적합한 페이지네이션 유형 선택법

페이지네이션 유형 선택은 데이터의 성격과 사용자 경험 요구사항에 따라 달라집니다. 다음은 모바일 개발의 일반적인 시나리오에 대한 권장 사항입니다. 보편적인 해결책은 없습니다 — 각 방식에는 최적의 적용 영역이 있습니다.

  • 채팅 / 메신저 — Cursor 페이지네이션(메시지 ID 기준). 새 메시지는 상단에 추가되며 커서가 흐트러지지 않습니다.
  • 뉴스 피드 — Time-based 페이지네이션(created_at 기준). 레코드는 시간순으로 정렬되며 시간 순서가 중요합니다.
  • 상품 카탈로그 — Offset 페이지네이션. 사용자가 특정 페이지로 이동할 수 있으며 데이터 변경이 드뭅니다.
  • 주문 내역 — Cursor 페이지네이션. 로드 사이에 새 주문이 추가되므로 안정성이 중요합니다.
  • 댓글 — Keyset 페이지네이션. 각 댓글에 고유 ID가 있으며 중복 없이 대규모 데이터를 처리합니다.

Paging 3을 사용한 Android 구현

Android Paging 3 라이브러리는 PagingSource를 통해 모든 페이지네이션 유형을 지원합니다. Offset의 경우 Int 키(page)를 가진 PagingSource, Cursor의 경우 String 또는 Long 키(cursor)를 가진 PagingSource를 사용합니다. PagingSource는 로딩, 캐싱, 오류 시 재시도를 자동으로 관리합니다.

kotlin
class PostPagingSource(
    private val api: PostApi
) : PagingSource<Long, Post>() {

    override suspend fun load(
        params: LoadParams<Long>
    ): LoadResult<Long, Post> {
        val cursor = params.key ?: Long.MAX_VALUE
        return try {
            val response = api.getPosts(cursor, params.loadSize)
            LoadResult.Page(
                data = response.items,
                prevKey = null,
                nextKey = response.items.lastOrNull()?.id
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Long, Post>): Long? {
        return state.anchorPosition?.let {
            state.closestItemToPosition(it)?.id
        }
    }
}

페이지 크기 권장 사항

페이지 크기는 로딩 속도와 성능 인식에 영향을 미칩니다. 모바일 앱의 경우 최적 범위는 페이지당 10~25개 항목입니다. 10개 미만은 API 요청이 너무 많아지고 스크롤이 끊깁니다. 25개를 초과하면 느린 네트워크에서 첫 번째 청크 로딩이 느려집니다.

이미지와 비디오의 경우 각 요소에 미디어 로딩 시간이 추가로 필요하므로 페이지 크기를 5~10으로 줄입니다. 텍스트 기반 목록(댓글, 로그)의 경우 크기를 30~50레코드까지 늘릴 수 있습니다. 클라이언트가 다양한 네트워크 조건에 적응할 수 있도록 API를 통해 페이지 크기를 설정 가능하게 하는 것이 좋습니다.

자주 묻는 질문

페이지네이션을 간단히 설명하면 무엇인가요?

페이지네이션은 데이터를 한 번에 모두 로드하지 않고 부분적으로 로드하는 것입니다. 책과 같아서 한 페이지를 읽고 다음 페이지로 넘어갑니다. 앱에서는 목록을 스크롤할 때 다음 데이터 청크가 로드되며 전체 목록이 로드되는 것이 아니므로 트래픽과 메모리를 절약합니다.

Offset과 Cursor 페이지네이션의 차이점은?

Offset은 레코드를 셉니다: “20개를 건너뛰고 다음 10개를 반환합니다.” 로드 사이에 새 레코드가 추가되면 번호가 밀려납니다. Cursor는 마지막 레코드의 고유 식별자를 사용합니다: “ID = 100 이후 10개 레코드를 반환합니다.” 새 레코드는 위치에 영향을 주지 않습니다.

페이지네이션의 최적 페이지 크기는?

모바일 앱의 경우 10~25개 항목이 최적입니다. 이미지가 많은 목록은 5~10개, 텍스트 피드는 20~30개입니다. 크기는 각 요소의 평균 크기에 따라 달라지며, 요소가 무거울수록 빠른 표시를 위해 페이지를 작게 해야 합니다.

RecyclerView에서 페이지네이션을 구현하는 방법은?

Android Jetpack의 Paging 3 라이브러리를 사용하세요. 로딩용 PagingSource, 반응형 스트림용 PagingData, 스크롤 시 자동 로딩용 PagingDataAdapter를 제공합니다. 이 라이브러리는 커스텀 PagingSource를 통해 Offset, Cursor, Keyset 페이지네이션을 지원합니다.

무한 스크롤이란 무엇이며 페이지네이션과 어떻게 다른가요?

무한 스크롤은 목록 끝에 가까워지면 새 데이터 청크가 자동으로 로드되는 UI 패턴입니다. 페이지네이션은 메커니즘으로 데이터를 청크 단위로 로드하는 방식이며, 무한 스크롤은 이를 표시하는 한 가지 방법입니다. 대안으로 “더 로드” 버튼이 있습니다.

요약

  • 페이지네이션 — 모든 유형의 목록을 가진 모바일 앱에 필수적인, 데이터를 청크 단위로 로드하는 기술.
  • Offset 페이지네이션은 구현이 간단하지만 삽입 시 불일치와 큰 OFFSET에서 성능 저하가 발생.
  • Cursor 페이지네이션은 고유 식별자로 탐색 — 모든 볼륨에서 안정적이고 효율적.
  • Keyset 페이지네이션은 기본 키로 필터링하여 인덱스 활용으로 최대 성능 제공.
  • Time-based 페이지네이션은 타임스탬프로 레코드 그룹화 — 시간순 피드와 소셜 네트워크에 이상적.
  • 방식 선택은 데이터 성격에 따라: 동적 데이터는 Cursor/Keyset, 정적 데이터는 Offset, 피드는 Time-based.
  • Android의 Paging 3과 iOS/웹의 표준 커서 솔루션은 모든 페이지네이션 유형에 대한 준비된 인프라 제공.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기