Rocky 's Blog

Notion API 초기 페이지 로딩 속도 개선

  • Notion API
2025. 12. 28.
게시글 썸네일

기본 상황


노션 API를 활용해 블로그를 만들고자 했다. 노션에서 작성한 글을 그대로 웹에 게시할 수 있고, 별도의 복잡함 없이 노션의 데이터베이스 기능으로 게시글을 관리할 수 있다는 점이 편리했다.

노션 API를 활용해 게시글 데이터베이스를 구성하고, 클라이언트에서 이를 조회하도록 구현했다. 데이터베이스에는 다음과 같은 속성을 설정했다.

게시글 데이터베이스
게시글 데이터베이스
  • name: 게시글 제목
  • category: 전체 | 회고 | 학습정리 | 트러블슈팅
  • date: 작성 날짜
  • tag: 관련 태그들
  • summary: 간략한 요약
  • 아래와 같이 데이터베이스를 기반으로 전체 게시글 수와 카테고리별 게시글 수를 표시하도록 만들었다.

    Notion image

    또한 한 페이지당 10개의 게시글을 불러오고, 페이지 이동을 위해 페이지네이션 기능을 구현했다.

    Notion image

    문제점


    초기에는 간단하고 직관적인 구조였다. 하지만 게시글 수가 증가하고 초기 로딩 속도를 개선하려던 과정에서 두 가지 문제점을 발견했다.

    1. 페이지 네이션

    현재 상황

    페이지네이션을 구현하기 위해서는 정렬된 게시글 목록 중 특정 인덱스 범위(예: 21~30번째 게시글)를 가져와야 한다. 하지만 노션 API는 정렬된 상태에서 임의의 구간을 직접 지정해 가져오는 기능을 제공하지 않는다.

    노션 API는 page_sizestart_cursor 매개변수를 제공하지만, 이는 커서 기반 페이지네이션이다. "3페이지(21~30번째)"라는 인덱스 기반 접근을 보장할 수 없다. start_cursor는 특정 인덱스가 아닌 이전 응답의 마지막 항목을 가리키기 때문이다.

    결국 전체 데이터를 한 번에 조회한 뒤 클라이언트 측에서 필요한 부분만 나누어 표시하는 방식으로 처리했다. 현재는 API의 최대 제한인 100개 단위로 데이터를 가져오도록 구현되어 있다.

    문제점

    처음에는 큰 문제가 없어 보였다. 그러나 데이터 패칭 방식에 의문이 생겼다. 전체 데이터를 매번 가져오는 것은 비효율적이었다. 실제로 29개의 게시글 데이터를 요청했을 때, 응답으로 약 3800줄에 달하는 JSON이 전송되는 것을 보고 경악했다.

    Notion image

    노션 API는 각 페이지의 상세한 블록 정보까지 포함해 응답하기 때문에 데이터량이 급격히 증가한다. 게시글이 50개, 100개로 늘어날수록 초기 로딩 시간이 길어지고 네트워크 비용도 증가할 것이 명확했다.

    필터 조회를 통해 특정 속성만 가져오는 방식으로 개선할 수는 있지만, 페이지네이션을 위해서는 여전히 전체 게시글을 조회해야 한다는 근본적인 문제는 동일했다.

    2. 총 게시글 수의 계산

    현재 상황

    페이지 상단에는 총 29개의 글 처럼 게시글의 총 개수를 표시하고 있다. 하지만 노션 API는 데이터베이스 전체 페이지 개수를 직접 반환하는 엔드포인트를 제공하지 않는다. COUNT(*) 같은 쿼리나 total_count 필드가 지원되지 않는다.

    따라서 현재는 앞서 조회한 전체 데이터 배열에서 results.length로 총 개수를 계산하고 있다. 처음에는 어차피 데이터를 모두 가져온다면, 이 시점에서 계산하면 되지 않을까? 라는 생각으로 페이지네이션 계산도 함께 처리했다.

    문제점

    원래라면 총 게시글 수는 단일 카운트 값만 가져오면 되는 상수 시간 연산이어야 한다. 하지만 현재 구조에서는 총 개수를 알기 위해 모든 게시글의 속성 데이터를 통째로 API로 요청하고 받아야 한다. 이 과정에서 네트워크 트래픽과 JSON 파싱 비용이 게시글 수에 비례해 증가한다.

  • 네트워크: 3800줄 JSON 전송
  • 클라이언트: 대용량 JSON 파싱 → length 읽기
  • 전체 비용: 게시글 수 N에 정비례
  • 특히, 필터링(카테고리 선택 등)을 적용할 때마다 전체 데이터를 다시 조회해야 한다. 물론 캐싱을 활용할 수는 있겠지만, 첫번째 로딩에서 오래걸리는 것은 마찬가지였다. 총 게시글 수를 별도로 관리하거나 효율적인 조회 구조로 개선해야 한다.

    사용자가 회고 탭을 클릭하는 순간, 또다시 방대한 양의 JSON이 네트워크를 통해 전송된다. 총 게시글 수를 별도로 관리하거나 효율적인 조회 구조로 개선해야만 했다.

    시도한 방법 1


    시도한 부분

    첫 번째 시도는 메타데이터베이스를 추가로 만드는 것이었다. 관계형 데이터베이스의 인덱싱 개념을 생각하며, 페이지당 10개 게시글 기준으로 10개씩 그룹화해 관리하도록 설계했다.

    Notion image
  • group: 페이지 그룹 번호
  • count: 그룹당 게시글 개수
  • posts: 해당 그룹의 게시글 ID 배열
  • 이렇게 구상한 이유는 다음과 같다.

  • 특정 페이지 그룹의 게시글만 선택적으로 불러와 네트워크 부하를 줄인다.
  • count 속성만 조회해 총합으로 전체 게시글 수를 빠르게 계산한다.
  • group 속성으로 페이지네이션에 필요한 총 페이지 수를 빠르게 파악한다.
  • 문제점

    데이터베이스를 분리해 네트워크 전송량을 줄일 수 있겠다고 생각했다. 하지만 구현에 들어가기 직전에 여러 근본적인 한계를 발견했다.

    모든 관리가 수동으로 이루어진다.
  • 새 게시글 등록 시 연관 메타데이터를 수동으로 추가해야 한다.
  • 게시글 삭제 시에는 영향을 받은 모든 그룹의 메타데이터를 재정리해야 한다.
  • 자동화가 어렵고 유지보수 부담이 크다.
  • 카테고리별로 필터링이 불가능하다.

    그룹화는 단순히 순차적 개수 기준일 뿐, 카테고리나 태그 같은 조건을 반영하지 않는다. 필터링 시 여전히 원본 데이터베이스의 전체 게시글을 조회해야 한다.

    데이터 조회 요청이 2배로 증가한다

    1페이지를 로드한다고 가정하면 두 번의 API 호출이 필요하다.

    1. 메타데이터베이스에서 해당 그룹의 페이지 ID 조회
    2. 해당 ID들로 원본 데이터베이스에서 실제 게시글 정보 조회

    정리

    이 방법은 근본적으로 올바르지 않다고 판단했다. 수동으로 관리하면서 번거로워진다는 점과 필터링이 불가능하다는 점이 가장 큰 문제였다. 자동화 스크립트를 만들 수 있겠지만, 무료 플랜으로만 사용하고 싶었다. 그래도 데이터베이스 분리라는 생각 자체는 효과적일 수 있겠다는 확신이 들었다. 더 나은 구조로 재설계하기로 했다.

    시도한 방법2


    시도한 부분

    데이터베이스 분리 방식을 유지하면서 카테고리를 기준으로 연관관계를 재설계했다. 카테고리별 데이터베이스를 별도로 구성하고, 원본 게시글 데이터베이스와 연결했다.

    게시글 데이터베이스
    게시글 데이터베이스
    카테고리 데이터베이스
    카테고리 데이터베이스
  • count: 해당 카테고리의 게시글 개수
  • category: 카테고리 이름
  • posts: 해당 카테고리의 게시글 ID 배열
  • postDates: 게시글 작성 날짜 배열 (날짜별 정렬용)
  • 이렇게 구상한 이유는 다음과 같다.

  • 카테고리별로 직접 조회 가능하며 필터링 문제를 해결한다.
  • count로 총 개수를 빠르게 파악하고, 필요한 게시글 ID만 가져와 네트워크 부하를 줄인다.
  • 노션의 관계 속성을 활용해 클릭 한 번으로 자동 동기화된다.
  • 문제점

    하지만 여전히 근본적인 한계가 남아 있다. 게시글 ID 목록을 얻은 후, 실제 게시글 내용을 가져오기 위해 별도의 두 번째 API 요청이 필요하다.

    정리

    첫 번째 시도보다 나아졌지만, 여전히 fetch 요청이 2배라는 문제가 해결되지 않았다. 네트워크 부하를 줄이려면 데이터 전송량을 최소화해야 하고, 요청 횟수를 줄이려면 단일 API 호출로 모든 정보를 얻어야 한다.

    이 두 목표 사이에서 트레이드오프를 고민하게 됐다. 데이터베이스 2개 + 2번 요청 vs 단일 데이터베이스 + 최적화 중 어떤 방향이 더 나은지 결정해야 했다. 결국 노션 API의 제약을 고려해 완전히 다른 접근을 모색하기로 했다.

    결론


    어떤 방법을 선택했는가?

    초반에 페이지네이션을 포기하고 무한 스크롤로 전환하기로 결정했다. 노션 API는 start_cursor를 제공해 자연스러운 무한 스크롤에 최적화되어 있었기 때문이다. 굳이 페이지네이션이 필수적이 아니라는 점도 생각했었다.

    이에 맞춰 데이터베이스 구조를 재설계하고 적용했다. 한 번에 10개씩 순차적으로 불러오며, 스크롤 시 다음 cursor로 이어서 데이터를 추가한다.

    Notion image

    이 방식을 통해서 다음과 같은 장점들을 택했다.

  • 초기 로딩 시 10개 게시글만 요청 (기존 대비 1/3 데이터량)
  • 스크롤 위치를 통한 사전 로딩으로 부드러운 UX
  • API 호출 1회 + 더 적은 데이터 응답으로 총 게시글 수 조회 가능
  • 필터링 시에도 동일한 패턴 적용 가능
  • 이전 고민들은 무의미했나?

    그렇지 않다. 무한스크롤로 결정은 했지만 페이지네이션일때의 문제를 해결하고 싶었다. 더 좋은 구조에 대해서 고민해볼 수 있는 좋은 경험이었다고 생각한다.

    외부 API는 항상 내가 원하는 기능을 제공하지 않는다는 점을 깨닫게 되었다. 주어진 제약 조건 안에서 최선을 찾아갈 줄 알아야겠다. 이 과정 속에서 이상적인 구조와 실현 가능한 구조가 항상 동일하지 않겠다는 생각이 들었다.

    데이터 전송량와 API 호출 횟수, 복잡한 구현과 기능적인 완성도, 자동화와 수동 관리 등 선택의 순간이 많았다. 각각의 장단점이 존재했지만 확실하게 분석할 줄 알아야한다고 느꼈다. 앞으로의 선택의 순간에서도 올바른 선택을 할 수 있도록 말이다.

    앞으로 다른 외부 API를 활용할 때도 이런 과정을 떠올리면서 도전해봐야겠다.