← ガイダンス一覧へ戻る

2受信箱の初期読込

Zettelkasten Inbox / ブロック詳細

Notionの3つのデータベースを1ページも残さず取り切り、未処理ノート・Tag・ABC Noteの3つの一覧を1回で返す、いちばん重い読み取り。ここで全部渡すから、このあとの選択操作は通信なしで動く。

抽象度 Lv.4(技術用語+システム上のやりとりまで/製品名・ライブラリ名は書かない)

1. 概要

受信箱の画面を開いたときに1回だけ走る、いちばん重い読み取り。 Notionの3つのデータベースを全部、1ページも残さず取り切って、 「未処理のノート一覧」「Tagの一覧」「ABC Noteの一覧」を1回の応答にまとめて返す。

ここで全部まとめて返してしまうのがこのアプリの根っこの設計で、 このあとTagを選んでもABC Noteを絞り込んでも、サーバーへの問い合わせは発生しない。 選択操作はすべてブラウザの中のデータで完結する。だから最初が重く、そのあとが軽い。

2. 位置づけ

1. アクセス確認 → 2. 受信箱の初期読込 → 3. 本文の取り出し/5. Tagの推薦/6. ABC候補

3. 処理内容(Lv.4)

4. なぜこの位置にあるか(設計意図)

ここより後ろに分割できない理由。「Tagを選ぶ → その配下のABC Noteだけを候補にする」という このアプリの中心の動きを成立させるには、TagとABC Noteの親子関係が最初から手元に揃っている必要がある。 Tagを選んでから配下を問い合わせる作りにすると、Tagを付け外しするたびに通信が走り、 「あれこれ試しながら決める」という使い方ができなくなる。

ここより前に置けないもの。未処理の定義(タグ欄もABC欄も空)はNotion側の絞り込みで表現されている。 全部取ってからアプリ側で選り分けるのではなく、Notionに絞り込ませてから受け取る。 受信箱に出るのは数十件でも、Literature DB全体は数百件あるため、この差は通信量に直接効く。

30秒という控えの長さ。短すぎるようだが、 保存・削除・Tag作成が起きたときは時間に関係なく即座に捨てられる。 つまり30秒は「自分の操作以外でNotion側が変わった場合に、どれだけ古い情報を見せてよいか」の上限であって、 自分の操作は必ず即時に反映される。

5. 詳細プロセス分解

工程主体やりとりの内容方向
2-1ブラウザ → 初期読込API画面表示の要求(+ 修正対象のID)
2-2初期読込API(内部)IDの形の検査
2-3初期読込API → メモリの控え30秒以内の控えの有無を確認
2-4初期読込API → NotionLiterature DBを条件つきで全件取得
2-5初期読込API → NotionTag DBを全件取得
2-6初期読込API → NotionABC DBを全件取得
2-7初期読込API(内部)3つを突き合わせ、説明文と並び順を作る
2-8初期読込API → メモリの控え30秒の期限つきで保存
2-9初期読込API → Notion(修正モード時)対象1件と全関連の取得
2-10初期読込API → ブラウザ3つの一覧+取得時刻

各行を文章にすると

2-1 画面が開くと、ブラウザはまず「いま何が溜まっているか」を丸ごと問い合わせる。 全体マップから「このノートを直したい」と飛んできた場合だけ、URLに対象の番号が付いてくる。

技術的には GET /api/bootstrap、 修正モードなら GET /api/bootstrap?noteId=…。 対象の指定を本文ではなくクエリ文字列に載せているのは、これが読み取りの要求だから。 画面側のURLは /?note=… で、そこから取り出してAPIへ渡している。

2-2 番号が付いていたら、まず形だけを確かめる。中身がNotionにあるかどうかは、まだ見ない。

技術的には 32桁の16進、または 8-4-4-4-12 のハイフン区切り、のどちらかに正規表現で一致するかを見る。 外れたら 400 と「全体マップから開き直してください。」を返す。 通ったら、ハイフンを一度全部外してから決まった位置に入れ直す正規化をする。 Notionは両方の表記を受け付けるので、同じページを2つの別物として数えてしまう事故を防ぐため、 このアプリの中では常にハイフン付きの形に揃えている。

2-3 直前の30秒以内に誰かが同じ問い合わせをしていたら、その結果をそのまま使い回す。 Notionへは行かない。

技術的には サーバーのメモリ上の変数に 「有効期限の時刻」と「中身」を持っているだけの、いちばん単純な形の使い回し。 ディスクにも外部にも書かない。実行環境が入れ替わると消えるので、 これは速さのための工夫であって、正しさを支える仕組みではない。

2-4 Notionへ「タグもABC Noteも付いていないノートを、新しい順に全部ください」と頼む。 これが受信箱に並ぶ中身になる。

技術的には POST /v1/data_sources/{id}/queryand でつないだ2つの条件(関連が空・関連が空)と、作成時刻の降順の並び指定を application/json で送る。 読み取りなのにPOSTなのはNotion側の仕様で、絞り込み条件をJSONで送るため。 Notionは1回に最大100件しか返さないので、「続きの目印」が返ってくる限り繰り返し呼ぶ (ページ送り)。件数の上限は設けていないので、対象が1,000件あれば10回往復する。

2-5 続けてTagの一覧を、条件なしで全部もらう。

技術的には 同じ形の要求を、絞り込み条件なしで投げる。 ここで一緒に受け取るのが「親アイテム」(Tagの階層)と「Out_Permanent」(配下のABC Note)と 「Number_In_Literature」(何件のノートに使われたかの集計値)。 集計値はNotion側が計算して返すので、アプリは数え直さない

2-6 最後にABC Note(知識ページ)の一覧を、最近さわった順で全部もらう。

技術的には 2-4・2-5・2-6 は同時ではなく、1つずつ順番に実行される。 3つは互いに依存していないので同時に投げることもできる(実際、全体マップの10番はそうしている)。 ここが直列なのは、Notion側の1秒あたりの要求数の制限に当たりにくくするためと読める。 そのぶん、初回の待ち時間は3つの合計になる。

2-7 3つの一覧を突き合わせて、画面がそのまま表示できる形に組み替える。 Tagには「このTagの主なABC Noteはこれ」という説明文が付き、よく使われるTagが上に来る。

技術的には TagのIDとABC Noteの題名を対応表(辞書)にしてから引き当てるので、 突き合わせは件数に比例する時間で終わる(総当たりにはしない)。 並び順は使用回数の降順、同数なら題名の日本語順。 題名の比較は日本語の並び順を指定して行う――指定しないと、 ひらがな・カタカナ・漢字の順が環境によって変わるため。

2-8 組み上がった一式を、30秒だけ覚えておく。

技術的には 「いまの時刻+30秒」を期限として保持する。 保存(8番)・削除(9番)・Tag作成(5番)が成功したときは、 期限を待たずにその場で捨てる関数が呼ばれる。 同じ関数が全体マップの5分の控えも一緒に捨てる。

2-9 全体マップから修正しに来たときだけ、その1件を個別に取り直して、 いま何のTagと何のABC Noteに結び付いているかを完全に把握する。

技術的には まず単体のページ取得。 ゴミ箱に入っていないか、本当にLiterature DBの下にあるかを確認し、違えば 400。 次に関連づけを全部たどる。ページ取得の応答には関連が最大25件しか入らず、 「まだ続きがある」という印だけが立つので、続きは専用の問い合わせで100件ずつ取り切る。 タグ側とABC側の2つを同時に取りに行く(ここだけは並行)。 この「開いた時点の完全な状態」が、8番で「開いてから変わっていないか」を判定する材料になる。

2-10 最後に、3つの一覧と取得時刻をまとめて返す。 ブラウザには「この応答は保存しないで」と伝える。

技術的には 200application/jsonCache-Control: no-store。ブラウザ側の fetch も 保存しない指定で呼んでいるので、二重に保存を止めている。 受信箱の中身は「いま何が残っているか」そのものなので、古い写しが表示されると 処理済みのノートがもう一度出てくる。それを確実に防ぐための指定。

ワークフロー図

← 図は横にスクロールできます

ブラウザ
初期読込API
メモリの控え
Notion API
2-1 GET /api/bootstrap(?noteId= は任意)
2-2 IDの形を検査(外れたら400)
2-3 30秒以内の控えがあるか
2-4 Literature DBを条件つき全件取得
2-5 Tag DBを全件取得
2-6 ABC DBを全件取得
2-7 3つを突き合わせて画面用に組み替え
2-8 30秒の期限つきで控える
2-9 (修正モード時)対象1件と全関連を取得
2-10 200 + Cache-Control: no-store

6. この分解から見えること

  • 「重い最初、軽いその後」に全振りしている。3つのDBを全件取るのは高くつくが、 そのあとTagを付け外ししてもABC候補が即座に切り替わる。 迷いながら決める作業の快適さを、初回の待ち時間で買っている。
  • 件数の上限が無いのが、いちばんの弱点。 2-4・2-5・2-6 のどれにも「最大何件まで」という打ち切りが無い。 ABC Noteが1,000件を超えたら、それだけで10往復以上。 Notion側が育つほど、受信箱を開く時間が伸び続ける作りになっている。
  • 直列と並行が混在している。2-4〜2-6 は直列、2-9 の中のタグとABCの取得は並行。 同じファイルの中で使い分けているので、これは書き間違いではなく意図的な選択と読める。 全体マップ(10番)は同じ3つのDBを並行で取っており、設計の判断が場所によって違う
  • キャッシュは正しさに関与していない。30秒の控えが古くても、 保存の直前(8番)に必ずNotionから取り直して照合するので、古い情報のまま書き込むことはない。 キャッシュは速さのためだけにある、と割り切られている。
  • 受信箱に並ぶのは先頭40件だけ。APIは全件返すが、画面が描くのは40件まで。 残りは取得したのに表示されない。 全部処理すれば次の40件が出てくるが、通信量としては最初から全部運んでいる。

7. 失敗時の挙動

  • IDの形が不正:400 + 「対象ノートの指定が正しくありません。全体マップから開き直してください。」。Notionへは行かない。
  • DBが連携に共有されていない:Notionが 404 を返し、それがそのまま利用者へ届く。 画面には「Notionを読み込めませんでした」と再読み込みボタン。
  • 取得の途中で失敗:3つのうち1つでも失敗すると、全体が失敗になる。 途中まで取れた分は捨てられ、控えにも残らない。次に開くとまた最初から。
  • 修正対象がゴミ箱にある/別のDBのページ:400 + 「対象はLiterature Notes DBのノートではありません。」
  • どこまで進んでから失敗すると何が残るか:このブロックは読み取りしかしないので、 Notion側には何も残らない。控えも「成功したときだけ」書くので、中途半端な控えもできない。

8. 未確認事項

  • Literature DBが何件になると、初回表示が実用に耐えなくなるか(打ち切りの仕掛けが無いので上限は未確認)
  • 2-4〜2-6 を直列にしている理由が、本当にNotionの回数制限を避けるためなのか(コードにその旨の説明は無い)
  • 受信箱の表示上限が40件であることが、意図なのか暫定なのか
  • 複数の実行環境が同時に動いたとき、30秒の控えがどう振る舞うか(Lv.5相当の確認が必要)
  • Notionの集計値(使われた回数)が、いつ更新されるか。反映が遅れた場合、推薦の順位がずれる

※ 2026年8月14〜15日時点の zettelkasten-inbox のプログラムを読んで作成しました。過去の設計資料は参照していません。

※ 番号は全体図・この解説・シーケンス図で共通です。工程番号は「ブロック番号-連番」で書いています。

全体図へ戻る / ブロック目次へ

akuramochi1.com — personal tools for learning and thinking.