【テクニカル・上級編】Notionの「データベースリミット」とデータ容量枯渇を防ぐ:数万件規模のアーカイブ運用と外部ストレージ連携の自動化設計 – プロジェクト・ナレッジ管理活用バイブル

Notionを数百万件の巨大スケールで酷使する:データベース限界突破と外部ストレージ自動連携のアーキテクチャ

開発チームのベロシティが上がると共に、Notionは単なるドキュメントツールから「開発の神経中枢」へと変貌を遂げる。要件定義、仕様書、APIスキーマ、そして膨大なログやテストエビデンス。しかし、ここで多くの組織が致命的な壁にぶ当たる。

「数万件を超えたデータベースの検索が異様に重い」
「画像やバイナリの添付でファイル容量のクオータ(制限)がすぐに枯渇する」

素朴にNotionを使い続けているうちは、この破滅的なボトルネックに気づかない。だが、真にアジャイルな組織のナレッジハブとしてNotionを極限まで使い倒すなら、その内部アーキテクチャの限界を理解し、低レイヤのハックと自動化パイプラインによってシステムを武装しなければならない。

本稿では、Notionのデータベース限界を突破し、数万件規模のアーカイブ運用と外部ストレージ(AWS S3)連携を完全に自動化する、極限のナレッジマネジメント・アーキテクチャを解説する。

—

1. Notionデータベースが数万件を超えた際のパフォーマンス劣化メカニズム

内部データ構造の呪縛

Notionのデータベースは、フロントエンドからは単なるリッチな表計算・カンバンボードに見えるが、その実体は「ドキュメントのツリー構造の中に埋め込まれた、動的なリレーショナル・グラフ」である。

数万件(数10万レコード)を超えた段階でパフォーマンスが急激に劣化する原因は、主に以下の3点に集約される。

1. クライアントサイド・レンダリング(CSR)の限界:
Notionのデスクトップ・Webアプリは、初期ロード時に一定数のページメタデータとプロパティをメモリ(DOM / JS Heap)上に展開しようとする。数万件のレコードを持つDBをフィルターなしで全件ロードしようものなら、ブラウザタブのメモリ消費量は容易に1GBを超え、ガベージコレクション(GC)の頻発によるフリーズを引き起こす。
2. リレーション(Relation)とロールアップ(Rollup)の再計算コスト:
複雑に張り巡らせられたリレーションや、他DBを参照するロールアッププロパティは、レコードが更新されるたびに依存グラフの再評価(Re-evaluation)を引き起こす。データ量が増えるほど、このグラフの深さと幅が広がり、Notionのバックエンド(APIサーバー群)でトランザクションのロック競合やCPUスパイクを引き起こす。
3. インデックス戦略の不在(ユーザー側からの制御不可):
RDBであれば `EXPLAIN` を叩いてインデックスを貼るチューニングができるが、Notionではユーザーが物理的なインデックスを制御できない。したがって、クエリの最適化は「不要なデータを視界から消す(=アーカイブする)」こと以外にアプローチが存在しない。

—

2. 古いデータを別のアーカイブ用データベースへ自動移行するワークフロー

「すべてのナレッジをひとつの巨大DBに集約する」という初期の設計思想は、スケールフェーズにおいては悪手である。アクティブなデータと、参照頻度の低いコールドデータ(アーカイブ)は、物理的にデータベースを分離し、DB自体のノードサイズ(レコード数)を常に一定の閾値以下に保つべきだ。

ここでは、Notion APIとTypeScriptを用いて、一定期間(例: 180日)更新のないタスクやドキュメントを、自動的に「Active DB」から「Archive DB」へマイグレーションするデーモンプロセスの設計を示す。

アーキテクチャ概要

[Active Database]
│
▼ (Cron / GitHub Actions: 毎日深夜実行)
[Migration Script (TypeScript)]
├─ 1. Query: 最終更新から180日以上経過したページを抽出
├─ 2. Create: Archive DBへ同一スキーマでページを複製
└─ 3. Archive: Active DB側のページを削除 (またはステータス変更)

自動移行スクリプト(TypeScript)

以下のスクリプトは、ページプロパティを完全に維持したまま別DBへ安全に移行する堅牢な実装である。

import { Client } from “@notionhq/client”;

// Notionクライアントの初期化
const notion = new Client({ auth: process.env.NOTION_API_KEY });

const ACTIVE_DB_ID = process.env.ACTIVE_DATABASE_ID!;
const ARCHIVE_DB_ID = process.env.ARCHIVE_DATABASE_ID!;
const RETENTION_DAYS = 180;

async function migrateOldPages() {
const thresholdDate = new Date();
thresholdDate.setDate(thresholdDate.getDate() – RETENTION_DAYS);
const isoThreshold = thresholdDate.toISOString();

console.log(`[Migration] Starting sweep for pages inactive since: ${isoThreshold}`);

let hasMore = true;
let startCursor: string | undefined = undefined;

try {
while (hasMore) {
// 1. アクティブDBから条件に合致する古いレコードをクエリ
const response = await notion.databases.query({
database_id: ACTIVE_DB_ID,
filter: {
and: [
{
timestamp: “last_edited_time”,
last_edited_time: {
before: isoThreshold,
},
},
{
property: “Status”,
status: {
equals: “Done”, // 完了しているものに限定
},
},
],
},
page_size: 50, // API制限とメモリを考慮したバッチサイズ
start_cursor: startCursor,
});

for (const page of response.results) {
if (!(“properties” in page)) continue;

const pageId = page.id;
console.log(`[Processing] Migrating page ID: ${pageId}`);

// 2. アーカイブDBへページを複製
// 注意: プロパティの型(Title, RichText, Select等)に応じたペイロード構築が必要
await notion.pages.create({
parent: { database_id: ARCHIVE_DB_ID },
properties: page.properties,
// ページ内のブロックコンテンツも移行する場合は別途 blocks.children.append が必要
});

// 3. アクティブDBから該当ページを削除(ゴミ箱行き)
await notion.pages.update({
page_id: pageId,
archived: true,
});

console.log(`[Success] Migrated and archived: ${pageId}`);
}

hasMore = response.has_more;
startCursor = response.next_cursor ?? undefined;
}
} catch (error) {
console.error(“[Fatal Error] Migration pipeline failed:”, error);
process.exit(1);
}

console.log(“[Migration] Completed successfully.”);
}

migrateOldPages();

これを `GitHub Actions` の `schedule` トリガー(cron: `0 2 ` など)で毎日実行することで、人間の手を介さずにDBの軽量化と高速化が常時維持される。

—

3. 重いバイナリのオフロード:AWS S3とNotionの完全連携パイプライン

数万件のデータベースよりも恐ろしいのが、「ドキュメント内に直接アップロードされる数テンバイトの動画、高解像度デザインカンプ、巨大なダンプファイル」である。Notionのストレージ容量や単一ファイルサイズ制限を圧迫するだけでなく、バックアップやAPI経由のエクスポート時にネットワーク帯域とタイムアウトの悪夢を引き起こす。

「Notionにはメタデータと軽量なMarkdown/リンクのみを置き、実ファイルはAWS S3などのオブジェクトストレージに逃がす」——これが大規模開発組織における唯一無二の正解である。

セキュアなアップロード&連携フロー設計

1. 開発者がCLIツールまたは専用Webhooks経由でファイルをアップロード。
2. バックエンドワーカーがファイルを受け取り、セキュアに AWS S3 へバーストアップロード。
3. S3上で発行された署名付きURL(Pre-signed URL)(またはパブリックCDN URL)を取得。
4. Notion APIを叩き、対象ページのプロパティ(URL型)または本文(Bookmarkブロック)にS3へのリンクを自動埋め込み。

S3アップロード & Notion連携スクリプト

以下は、ローカルの重いファイルをS3にプッシュし、そのアクセシブルなURLをNotionの指定ページに紐付けるための実践的なスクリプトだ。

import { S3Client, PutObjectCommand } from “@aws-sdk/client-s3”;
import { Client } from “@notionhq/client”;
import as fs from “fs”;
import as path from “path”;

const s3 = new S3Client({ region: process.env.AWS_REGION });
const notion = new Client({ auth: process.env.NOTION_API_KEY });

const BUCKET_NAME = process.env.S3_BUCKET_NAME!;
const TARGET_PAGE_ID = process.env.TARGET_NOTION_PAGE_ID!;

async function uploadAndLinkAsset(filePath: string) {
const fileStream = fs.createReadStream(filePath);
const fileName = path.basename(filePath);
const s3Key = `notion-assets/${Date.now()}-${fileName}`;

console.log(`[S3] Uploading ${fileName} to bucket ${BUCKET_NAME}…`);

// 1. S3へファイルをアップロード
const uploadParams = {
Bucket: BUCKET_NAME,
Key: s3Key,
Body: fileStream,
ContentType: “application/octet-stream”,
};

await s3.send(new PutObjectCommand(uploadParams));

// 2. アクセス用URLの生成 (CloudFront経由かパブリックS3URLを想定)
const fileUrl = `https://${BUCKET_NAME}.s3.${process.env.AWS_REGION}.amazonaws.com/${s3Key}`;
console.log(`[S3] Upload complete. URL: ${fileUrl}`);

// 3. Notionの該当ページにファイルリンクブロックを追加、またはプロパティを更新
await notion.blocks.children.append({
block_id: TARGET_PAGE_ID,
children: [
{
object: “block”,
type: “bookmark”,
bookmark: {
url: fileUrl,
caption: [
{
type: “text”,
text: { content: `[External Asset] ${fileName}` },
},
],
},
},
],
});

console.log(`[Notion] Successfully linked asset to page: ${TARGET_PAGE_ID}`);
}

// 実行例
// uploadAndLinkAsset(“./heavy-architecture-diagram.png”);

この設計により、Notionのデータベース容量を数KBの軽量なテキストとメタデータに抑え込みつつ、数ギガバイトに及ぶアセットをS3の無限の拡張性と安価なストレージコストで完全に管理下置くことが可能となる。

—

結び:ツールに振り回されるな、アーキテクチャでねじ伏せろ

Notionは強力なツールだが、それは「適切にデザインされたシステムの外枠」の中で運用されて初めて真価を発揮する。数万件のスケール、パフォーマンスの劣化、ストレージの枯渇。これらはNotionの欠陥ではなく、「スケールを無視した運用設計の怠慢」に他ならない。

今回紹介した「自動アーカイブパイプライン」と「S3オブジェクトオフロード」をあなたの開発パイプラインに組み込み、Notionを真のハイパフォーマンス・ナレッジベースへと昇華させよ。これこそが、圧倒的なベロシティを生み出すエンジニアリングだ。

タイトルとURLをコピーしました