Gatsby+Notion API 플러그인(gatsby-source-notion-feely) 작업기.

작성 : 2023-10-25수정 : 2023-10-29

목차 펼치기


머리말

GC 지원 종료로 인해 Netlify로 배포환경을 변경하는 작업의 복병은

gatsby-source-notion-api

플러그인이었다. Weezip의 스택을 구상하면서 가장 중요한 기능인

Gatsby와 Notion의 통합

을 담당해주는 플러그인인데, 갑자기 오류가 발생하여 빌드 자체가 안되기 시작했다. GC 종료는 일주일도 채 남지 않았다. 등줄기에는 식은땀이 흐르고 뇌는 현실을 부정하기 시작했다.


오류 메시지로는 Notion의 데이터베이스 ID와 토큰 값을 확인해보라는 문구뿐이었다. 처음에는 혹시나 싶어 Notion API의 공식 문서를 확인하여 포스트맨으로 호출해보니 정상적으로 동작했다. ID와 토큰에는 문제가 없다. 그렇다면 대체 뭐가 문제일까 싶어 처음으로 내부 코드를 들여다보기 시작했고, Notion API 통신간 발생하는 모든 오류 메시지는 저 문구로 통일되어 있다는 것을 알게 되었다. 내 상황에 맞는 오류 메시지가 아니었던 것이다.


코드의 기본 로직은 다음과 같았다.

  1. Notion 데이터베이스 pages API 호출

  2. 해당 페이지 내의 block API 호출

  3. 만약, 중첩된 block이 존재하는 구조일 경우 block 내부에서 다시 block API 호출

  4. 생성된 데이터로 마크다운 문서 생성

  5. 기타 적절한 데이터 파싱

  6. 페이지 별 Node 생성


내부 코드에 로그를 추가해서 디버깅한 결과, 간헐적으로 2번 또는 3번 동작에서 오류가 발생했다. 빌드할 때마다 다른 포인트에서 오류가 발생했는데, 이는 데이터의 문제라기보다는 네트워크에서 발생하는 문제라고 생각하게끔 만들었다. 이 이슈와 관련해서는 해당 플러그인에

Issue

티켓을 생성해서 공유했지만, 내게 가장 중요했던 것은

어떻게든 서비스가 중단되지 않는 것

이었다.



죽지만 말자

우선 해당 리포지토리를 fork해 왔다. 0BSD 라이센스였기 때문에 복제 및 수정 등에 자유로워서 다행이었다. 이후 약간의 디버깅을 거치며 다음과 같은 의사결정을 했다.


전체 페이지 중 일부 페이지의 일부 블록이 누락될 위험이 있지만, 이 오류는 무시하기로 한다.

현실적으로 서비스 유지를 위한 유일한 방법이었다. 오류가 발생할 경우

repoter.panic

이 아닌 단순히

console.error

가 실행되도록 했다. 이후 Weezip 리포지토리의

package.json

gatsby-source-plugin-api

라이브러리의 경로를 내 리포지토리로 변경해주었다.


json
1"gatsby-source-notion-api": "github:dearlsh94/gatsby-source-notion-api",

Weezip package.json


원래라면 버전이 명시되어 있어야 하는 곳을 github 경로로 수정해주면 된다.


이후 빌드 해 보니 정상적으로 빌드가 되어 배포 환경을 무사히 이관할 수 있었다. 물론, 빌드 1번 당 전체 문서 내용 중 약 6~7 개의 블럭이 보이지 않는 문제만 제외한다면 말이다.


이 문제는 다시 숨을 고르고 수정하기로 했다. 어차피 일 평균 방문자 1~5명인 내 블로그 내의 일부 블럭이 안 보이는 것 정도야 큰 문제가 아니다.



그래서 뭐가 문제야

네트워크 문제라는 심증을 가지고 Notion API 문서들을 살펴보던 중 문제의 원인으로 99% 확신할 수 있는 부분을 발견했다. 바로

Request limits

제약 조건이다. Notion 측은 API 성능을 보증하기 위해 시간과 크기에 대한 제한을 가지고 있는데, 높은 확률로 이 시간 조건에서 문제가 발생했던 것으로 보인다.


Rate-limited requests will return a "rate_limited" error code (HTTP response status 429). The rate limit for incoming requests per integration is an average of three requests per second. Some bursts beyond the average rate are allowed.

블로그를 운영하며 글이 많아지면서 호출해야하는 총량이 늘어나고, 중첩구조가 자주 사용되면서 429 오류가 발생했을 것으로 보인다.



개발할 결심

이 플러그인은 2022년 7월 30일이 마지막 릴리즈로, 이후 약 1년이 넘는 시간동안 변경되지 않았다. 발행한 Issue에 대해서 빠르게 답변은 해주셨지만, 아무래도 내 필요를 더 정확히 충족하기 위해서 개발할 결심을 했다. 기존 리포지토리에 Contributor로 참여할 수도 있었지만, 자유를 위해 분리하는 것을 선택했다.


Feature
  • 여러 개의 Notion 데이터베이스를 연결할 수 있도록 한다.

  • Notion API 통신 간 발생하는 오류에 대해 더 명확한 오류 메시지를 제공한다.

  • 429 오류에 대응하여 Block을 가져오는 로직에 retry 기능을 추가한다.

  • 기존 마크다운 생성 로직을 제거한다.

    • 이는 추후에 다시 고려한다.

  • Notion API 버전을 최신화한다.

  • Publish 체크 기능 추가



플러그인 개발환경

나는 플러그인을 개발해본 경험이 없었지만, 기존의 코드를 바탕으로 수정하는 것이기 때문에 기본적인 환경설정이 되어 있어 비교적 수월히 작업에 착수할 수 있었다. 만약 처음부터 개발해야된다고 하면, Gatsby에서 제공해주는 Starter를 사용해서 초기 구축을 진행하면 될 것으로 보인다.


플러그인을 개발하는 환경에서는 테스트 라이브러리를 사용해서 트리거할 수 있다. 기존에는

ava

라이브러리를 사용하고 있었으나, 나는

Jest

로 테스트 라이브러리를 변경했다.

Jest

는 기본적으로

test.js

로 끝나거나,

/test

디렉터리 안에 있는 파일들은 모두 테스트 파일로 인식한다.


Jest

를 통한 자세한 설정 방법은 Gatsby의 공식 가이드를 참고해서 진행했다. 큰 어려움 없이 보이는 코드들을 순서대로 복사하며 설정해주면 되었다. API Token과 데이터베이스 ID를 설정해주기 위해

dotenv

를 활용해 env 설정까지 해주었다.


  1. 라이브러리 설치

    bash
    1yarn add -D jest babel-jest babel-preset-gatsby identity-obj-proxy dotenv
  2. jest.config.js

    파일 생성

  3. jest-preprocess.js

    파일 생성

  4. jest-setup.js

    파일 생성

  5. .env.test

    파일 생성 및 키 추가

  6. /test/gatsby-node.test.js

    파일 생성


테스트 코드를 구현할 때 Gatsby에서 플러그인 호출 시 전달하는

actions

,

reporter

,

cache

에 대한 mock 객체를 생성해서 Gatsby에서 호출하는 것과 같이 동작할 수 있도록 작성했다. 이 핸들러의 정확한 구조는 공식 문서에서 확인할 수 있다.



Features

정리했던 기능 구현을 위한 작업에 대한 간단히 정리한 내용이다. 각 번호는 작업 순서와는 무관하다.


1. 여러 Notion 데이터베이스 연결

javascript
1{
2	databaseIds: [, ],
3	aliases: ['Book', 'Movie']
4}
5
6{
7	token: process.env.NOTION_INTEGRATION_TOKEN,
8	databases: [
9		{
10			id: process.env.NOTION_DB_ID,
11			name: `Weezip`,
12			isCheckPublish: true,
13		},
14		{
15			id: process.env.NOTION_DB_ID_2,
16			name: `Treefeely`,
17		},
18	],
19}

이 기능을 개발하기 위해서는 플러그인에 전달되는 데이터베이스 ID 값이 복수로 전달되어야 한다. 따라서 플러그인의 매개변수를 배열로 수정했다. 내부에서는 여러 데이터베이스의 페이지들을 1차원 배열로 반환하기 때문에, 이를 구분해줄 alias를 따로 지정할 수 있도록 했다.

위의 코드를 보면

NOTION_DB_ID

내부 페이지들은

Weezip

이라는

databaseName

을 가지고,

NOTION_DB_ID_2

데이터베이스 내부 페이지들은

Treefeely

를 가지게 된다. 이 페이지들은 모두 1차원 배열에 함께 담겨서 반환된다.


2. Notion API 통신 간 오류 전달 개선

기존의 오류 메시지를 수정하여 Status 코드와 공식 문서 경로를 보여준다. 사용자는 공식 문서를 통해 해당 Status 코드가 어떤 오류인지 더 자세히 파악할 수 있다.


3. Notion API 429 오류 retry 기능 추가

오류 Status가 429일 경우 15초를 기다린 후 최대 4번 더 호출하는 로직을 추가했다.

javascript
1if (object === "error") {
2	// rate_limited
3	if (status === 429) {
4		const sleep = (ms) => {
5			const wakeUpTime = Date.now() + ms;
6			while (Date.now() < wakeUpTime) {}
7		};
8
9		while (tryCount <= 5) {
10			reporter.warn(`[${status}] rate limited! retry after 15s (${tryCount}/5)`);
11			sleep(1000 * 15);
12			return await fetchBlocks({ id, notionVersion, token }, reporter);
13		}
14	}
15	throw new Error(`[${status}] ${errorMessage}`);
16}

4. Markdown 생성 로직 제거

MD 문서 형식이 더 간편하게 사용할 수는 있지만, 나는 노션의 각 블럭마다 대응되는 컴포넌트를 구현하는 식으로 블로그를 구현했기 때문에 Markdown을 사용하지 않았다. 불필요한 Markdown 데이터를 생성하는 과정에서 시간도 지연될 수 있어 깔끔하게 모두 제거했다.


5. Notion API 버전 최신화

Notion은 API를 호출하면서 Headers 정보에

Notion-Version

yyyy-MM-dd

형식의 문자열을 넘기고 이를 통해 API의 버전을 체크한다. 기존 라이브러리에서 사용하고 있는

2021-05-13

버전에서

2022-06-28

버전으로 변경했다.


6. Publish 체크 기능 추가

이 부분은 데이터베이스의 모든 페이지를 가져온 후, 프로젝트 환경에서 filter하여 사용할 수도 있지만 이번 작업하면서 겸사겸사 플러그인의 내장 옵션으로 개발해봤다. 다만 명확한 사용 조건이 존재한다. 기능의 메커니즘은 다음과 같다.


checkPublish 값으로 true 를 전달할 경우, Notion 데이터베이스 내 is_publised 라는 컬럼의 값이 true 인 페이지만 조회한다.

Notion API는 데이터베이스를 조회할 때 필터 쿼리를 추가할 수 있어 이를 활용했다.



gatsby-source-notion-feely 리포지토리

이 모든 작업은 우선

gatsby-source-notion-api

리포지토리를 fork한 후

develop

브랜치를 생성하여 진행했다. 기본적인 테스트까지 완료한 후 fork한 리포지토리를 새로 import 하는 과정을 통해 fork 관계를 해제해주면서

gatsby-source-notion-feely

라는 별도의 리포지토리로 분리했다.

develop

에 개발해 둔 내용을

main

브랜치에 머지한 다음 Weezip에서 이를 설치해서 실제 테스트를 진행했다.


json
1"gatsby-source-notion-feely": "github:dearlsh94/gatsby-source-notion-feely",

package.json

을 위와 같이 설정하면 된다. 일부 문제가 있었지만 디버깅과 수정을 거쳐

gatsby-source-notion-feely

플러그인을 사용한 Weezip이 로컬 환경에서 정상적으로 실행되었다.


이후 npm에 플러그인이 정상적으로 배포되면, 다른 라이브러리들처럼 github 경로가 아닌 npm에 배포된 버전으로 사용할 수 있다.


생성되지 않은 Gatsby Node 문제

테스트를 해보니 Gatsby Node가 제대로 생성되지 않는 문제가 발생했다. 문제를 확인해보니, Gatsby Actions에서

createNode

함수를 호출할 때의 필수 값을 잘못 지워버려서 발생한 문제였다. 필요없어 보여서 지웠는데, 내부적으로 중요한 값이었던 것이다.

createNode

의 파라미터를 수정하기 전에는 공식 문서를 살펴보자.



꼬리말

이제 npm에 배포하고 라이브러리의 경로를 변경해주는 작업을 진행해야 하는데 README 문서와 코드를 조금 더 가다듬고 배포할 생각이다. 그 전까지 당분간은 Github 경로로 라이브러리를 설치하기로 하자.


처음부터 온전히 내가 개발한 것은 아니지만, 이 과정을 통해 개발자의 오픈 소스 생태계에 대해 다시금 생각하게 되었고 그 안으로 한 발자국 정도는 더 들어온 느낌이다. 앞으로도 꾸준히 오픈 소스에 관심을 가지고 더 많은 기여를 하고 싶은 욕심이 든다.


GC의 종료와 외부 라이브러리의 한계, 그리고 API에 대한 의심 등 이번 일을 겪으며 외부에 의존적인 서비스가 가질 수 밖에 없는 안정성의 불안에 대해 더 생각해보게 되었다.



더 읽어보면 좋은 글

    • 이 작업을 진행하며 화해 팀의 기술 블로그에도 Notion API를 활용한 정적 페이지 생성과 관련된 글이 있다는 것을 발견하게 되었다.

    • 나는 적당히 사용만 했던 부분들에 대해서 기술적으로 또 구조적으로 고민한 것 같은 글이라 재미있었다.

Wanna get in touch?

All Icons byiconiFy