[{"data":1,"prerenderedAt":307},["ShallowReactive",2],{"content-/stats-ssot-to-cloudflare-d1":3,"all-pages-for-dir":284,"related-/stats-ssot-to-cloudflare-d1":285,"og-image-/stats-ssot-to-cloudflare-d1":306},{"id":4,"title":5,"body":6,"category":263,"concepts":264,"description":265,"extension":266,"meta":267,"navigation":268,"ogImage":264,"path":269,"project_name":270,"published":271,"publishedAt":272,"seo":273,"source":274,"stem":275,"tags":276,"todo":264,"unpublished":271,"updatedAt":264,"__hash__":283},"pages/2026-10/2026-10-05/stats-ssot-to-cloudflare-d1.md","月次統計のSSOTをGitのTSファイルからCloudflare D1へ移す：Turso推奨を過去の検討資料で改めた日",{"type":7,"value":8,"toc":250},"minimark",[9,14,18,22,25,28,31,34,37,41,44,47,50,53,56,59,63,66,74,78,81,84,104,110,113,120,123,131,134,137,141,144,168,171,174,185,188,191,194,200,207,211,214,221,224,227,230,233,244,247],[10,11,13],"h1",{"id":12},"月次統計のssotをgitのtsファイルからcloudflare-d1へ移す","月次統計のSSOTをGitのTSファイルからCloudflare D1へ移す",[15,16,17],"p",{},"前日分の日記を作ってもらっている途中で、報告の一文に引っかかった。\n再発防止のガードを足したと書いてあるのに、同じ報告に「データの差分なし」とも書いてある。\n直すべきことがあったのに、なぜデータには差分が無いのか。\nこの疑問を口にしたところから、月次統計の置き場所を動かす一日が始まった。",[19,20,21],"h2",{"id":21},"発端は前日の日記への2つの指摘",[15,23,24],{},"1つめは、日記の材料に個人のメールアドレスが実名のまま出ていたことだ。\n「実名のままやめといてくださいね」と頼み、2月の記事に残っていたアドレス5か所も伏せてもらった。",[15,26,27],{},"2つめが、さっきの疑問である。\n「再発防止が意味がわからないんですけど、なんでこれデータの差分がないんですか」と聞いた。",[15,29,30],{},"返ってきた答えでは、「データの差分なし」は「何も起きなかった」という意味ではなかった。\n報告のほうが、まぎらわしい書き方をしていたのだという。\nガードの中身は、外銷訂単（台湾の輸出受注）の取得元が巻き戻ったときに SSOT を書き換えない、というものだ。\nその日の古い CSV で実際のスクリプトを回し、再現も取ってもらった。",[15,32,33],{},"そして、説明のついでに出てきた事実のほうが大きかった。\n統計データは全部が DB に入っているわけではなく、外銷訂単は Git の TS ファイルにしか無い。",[15,35,36],{},"「そうなんだ、Gitの.tsファイルなんだ」と返した。",[19,38,40],{"id":39},"turso-か-d1-か過去の計画を読むまで","Turso か D1 か、過去の計画を読むまで",[15,42,43],{},"そこで「DBに全部入れませんか？そろそろ」と持ちかけた。\n候補は Cloudflare の DB（D1）か Turso で、どちらがいいかを聞いた。",[15,45,46],{},"最初の答えは Turso だった。\n統計データはサイトの表示時に DB へ問い合わせるわけではなく、手元で TS ファイルを作るときに読むだけなので、D1 の強みが活きない、という理由である。",[15,48,49],{},"筋は通っている。\nただ、この話は前にも検討した覚えがあった。\n非公開のドキュメントを作った気がするので、それも踏まえてほしいと頼んだ。",[15,51,52],{},"非公開記事と memo の両方を探してもらうと、過去の検討が3件見つかった。\n本命は9月25日の「make-diary の統計取得を Cloudflare Workers へ移す計画」だった。\n状態は進行中で、Phase 0 まで終わっていた。\nその計画のために、統計用の D1 データベースもすでに作ってあった（テーブルは0本）。",[15,54,55],{},"取得の実行場所を Cloudflare へ寄せる計画が動いていて、器の D1 まで用意済みだった。\nこれを読んだ結果、Turso という最初の答えは改まり、推奨は D1 に変わった。",[15,57,58],{},"計画書は Codex に3回レビューさせ、3回目で「実装に進める」という判定をもらった。\n判断事項の3件は、Chrome に開いた計画書のボタンで選んで送った。\n3件とも推奨どおり A にした。\nDB の置き場所は D1 にし、ビルドが読むのはこれまでどおりコミット済みの TS にする、といった内容である。",[19,60,62],{"id":61},"引き継ぎを書き出して続きを始める","引き継ぎを書き出して、続きを始める",[15,64,65],{},"ここで一度区切ることにした。\n引き継ぎのプロンプトを、ダウンロードディレクトリのテキストファイルに書き出してもらった。\npush も許可した。\nこのセッションの5本に加えて、セッション開始時点で未 push だった2本も一緒に送られ、計7本になった。",[15,67,68,69,73],{},"そのあと ",[70,71,72],"code",{},"/compact"," で文脈を畳み、書き出したテキストファイルのパスを渡して続きを始めた。",[19,75,77],{"id":76},"phase-0d1-の器と書き込みルールを作る","Phase 0：D1 の器と書き込みルールを作る",[15,79,80],{},"最初に、1本にまとまっていた統計のエクスポートファイルを系列ごとのファイルに分けてもらった。\n分割の前後で export される値が一致することと、dev で各系列が同じ期間で表示されることを確かめてから、コミットした。",[15,82,83],{},"続いて D1 側の器を作った。",[85,86,87,91,98],"ul",{},[88,89,90],"li",{},"スキーマは3テーブルと1インデックスで、再適用しても同じ結果になる",[88,92,93,94,97],{},"書き込みの判定は純粋関数（",[70,95,96],{},"planUpserts","）に閉じ込め、SQL の組み立ても純粋関数にした",[88,99,100,103],{},[70,101,102],{},"d1.mjs"," は wrangler を呼ぶだけの薄いシェルにした",[15,105,106,107,109],{},"途中で、D1 への読み取りが認証エラーで通らないことがあった。\n試し直す前に issue を作ってもらい、そのあと ",[70,108,102],{}," が動くこと、数値のリテラルが崩れずに戻ることを確かめた。",[15,111,112],{},"テストで1件だけ落ちたが、原因はテストの側だった。\n空の値だけの取得結果は「巻き戻し」として扱うルールがあり、テストがそれに引っかかっていた。\n同じ月の金額の行を足し、本来確かめたいルールだけを見るテストに直してもらった。",[15,114,115,116,119],{},"コミットのところで、もう1つつまずいた。\nコミット自体は通ったのに、ゲートの Codex レビューが途中で失敗し、未レビューのまま保存されていたのだ。\n原因は Codex 側のモデルの容量不足（",[70,117,118],{},"Selected model is at capacity","）だった。\nissue に記録したうえで、同じ差分に手でレビューをかけ直してもらった。",[15,121,122],{},"かけ直したレビューから修正点が出てきた。\n主なものは次の2つである。",[85,124,125,128],{},[88,126,127],{},"月や系列が消えていたら、生成を止めるチェックを足す",[88,129,130],{},"往復のテストを、D1 の行の形を通して確かめる",[15,132,133],{},"Phase 0 は、計画書のチェック8項目がすべて埋まり、全テスト（265ファイル、21,133件）が通ったところで終えた。\nコミットは4本である。",[15,135,136],{},"完了の報告が英語で来たので、「日本語で返して」と頼んだ。\nこの日はこれを2回言った。",[19,138,140],{"id":139},"phase-1取得スクリプト4本を-d1-経由に切り替える","Phase 1：取得スクリプト4本を D1 経由に切り替える",[15,142,143],{},"対象は、外銷訂単、シンガポールの NODX、日本の半導体輸出、機械受注の4本だ。\nどれも「取得するたびに TS を丸ごと作り直す」型だったので、先に D1 へ書き込み、TS は D1 から生成する流れに変えた。",[15,145,146,147,150,151,154,155,154,158,154,161,164,165,167],{},"同期の処理（",[70,148,149],{},"sync.mjs","）と、CLI（",[70,152,153],{},"seed"," / ",[70,156,157],{},"generate",[70,159,160],{},"set-field",[70,162,163],{},"status","）を作ってもらった。\nWindows ではコマンドライン引数で日本語を渡すと化けることがあるため、",[70,166,160],{}," は値をファイルからも読めるようにした。",[15,169,170],{},"初回の投入では、TS ファイルのハッシュを前後で記録し、ファイルが変わらないことを確かめた。\n外銷訂単は768行を入れて、ハッシュは変わらなかった。\n残りの3ファイルも同じ手順で入れた。",[15,172,173],{},"実データでも、安全装置が効くかを見た。",[85,175,176,179,182],{},[88,177,178],{},"手で TS を編集した状態だと、手編集の検知が exit 4 で止める（ファイルは戻し、ハッシュが元と一致した）",[88,180,181],{},"取得元が巻き戻った系列は D1 に書かれず、TS のハッシュも変わらない（exit 3）",[88,183,184],{},"NODX は、ヘッダのコメント2行と生成時刻だけが変わり、値の差は無かった",[15,186,187],{},"一方で、スクリプトによる文字列の置換は何度も空振りした。\n一致しなかったり、エスケープで失敗したり、ファイルが CRLF のせいで複数行の一致が取れなかったりした。\nいずれも何も書き込まれていなかったので、Edit で1か所ずつ直してもらった。",[15,189,190],{},"数分前の作業で、0 から 0 への無意味な「改訂」記録が4件できていた。\nこれは消してもらい、再実行しても0件のままであることを確かめた。",[15,192,193],{},"機械受注は形式が変わったので、ページも目で見た。\nグラフと表は分割前と同じに描画され、最新月の 2026-07 は1兆102億円、+61.1% だった。",[15,195,196,197,199],{},"コミットゲートでは何度も止まった。\n2回目は、最新月をデータから導くことと、",[70,198,160],{}," のチェックを書き込みの前に移すことを指摘された。\n5回目は、手順書が exit 3 を一律に「変更なし」として扱っている点だった。\nその合間には、コミットメッセージに入れたバッククォートがテンプレート文字列を壊し、構文エラーになった。\nテスト用に書き換えた値（103.95）が外銷訂単の TS に残っていたことも見つかり、TS はバックアップから戻してもらった。",[15,201,202,203,206],{},"ようやくコミットできたのが ",[70,204,205],{},"6d613308"," で、全テスト（266ファイル、21,138件）も通った。",[19,208,210],{"id":209},"codex-のレビューは済んでいるのか","Codex のレビューは済んでいるのか",[15,212,213],{},"push のあと、実装に Codex のレビューがちゃんと通っているかが気になって聞いた。",[15,215,216,217,220],{},"答えは「ほぼ済んでいる」だった。\n今日の実装コミット5本のうち3本はコミットゲートで正規に通っていた。\n残りの2本は、Codex 側が混雑していて、ゲートのレビューが取れないままコミットされていた。\n抜けていた ",[70,218,219],{},"09b4bfa0"," にも事後レビューをかけてもらい、5本すべてが Codex のレビューを経た状態にした。",[19,222,223],{"id":223},"残りを片付ける",[15,225,226],{},"このセッションの未了事項を聞くと、6件あった。\n手順書の push と、走らせたままの dev サーバーの停止を頼み、あわせて公開記事に残っていたものを消すことにした。",[15,228,229],{},"残っていたのは、Workers のサブドメインと個人の Gmail である。\n伏せ字に置き換えると、コードブロックの外にある裸の URL 3つが、存在しないドメインへのリンクになってしまう。\nそこで、この3つはバッククォートで囲んでもらった。\nリポジトリ自体は非公開なので、memo や非公開記事は外から見えない。\n今回直したのは公開記事だけで、8ファイルになった。",[19,231,232],{"id":232},"学び",[85,234,235,238,241],{},[88,236,237],{},"「データの差分なし」という報告は、「何も起きなかった」ことを意味しない。引っかかった一文は聞き返す。今回はそこから、統計の正本が Git の TS ファイルだと分かった",[88,239,240],{},"朝の「Turso」という答えは、9月25日の計画と、すでに作ってあった D1 を読んだら改まった。過去に検討した覚えがあるなら、資料を探させてから決める",[88,242,243],{},"データを移し替えるときは、TS ファイルのハッシュを前後で記録しておけば、ファイルが変わっていないことを確かめられる",[19,245,246],{"id":246},"これから",[15,248,249],{},"Phase 1 には、まだ1つ項目が残っている。\n10月12日までの1週間、朝の統計チェーンの中で様子を見る。\n新しいデータが無い日に TS の差分が出ないか、手編集の検知が誤ってチェーンを止めないか。\nD1 を正本にした仕組みが本当に静かに回るのかは、この1週間を過ぎないと分からない。\nメモリ価格を移す Phase 2 は、そのあとになる。",{"title":251,"searchDepth":252,"depth":252,"links":253},"",2,[254,255,256,257,258,259,260,261,262],{"id":21,"depth":252,"text":21},{"id":39,"depth":252,"text":40},{"id":61,"depth":252,"text":62},{"id":76,"depth":252,"text":77},{"id":139,"depth":252,"text":140},{"id":209,"depth":252,"text":210},{"id":223,"depth":252,"text":223},{"id":232,"depth":252,"text":232},{"id":246,"depth":252,"text":246},"dev",null,"「なぜデータの差分が無いのか」という疑問から、月次統計の正本がGitのTSファイルだと分かった。TursoとCloudflare D1を比べ直し、過去の計画を読んでD1に決めて、取得スクリプト4本をD1経由に切り替えるまでの記録。","md",{},true,"/stats-ssot-to-cloudflare-d1","mdx-playground",false,"2026-10-05T00:00:00.000Z",{"title":5,"description":265},"make-diary","2026-10/2026-10-05/stats-ssot-to-cloudflare-d1",[277,278,279,280,281,282],"Cloudflare D1","Turso","SSOT","統計データ","Claude Code","Codex","VSkGZ6XDRLXLOPcSDItHTunrQ8FMfEI08gxf55c3-fk",[],[286,290,294,298,302],{"title":287,"path":288,"publishedAt":289},"TypeScriptにしか無かった統計をCloudflare D1へ移し切る：「全部入ったのか」と確かめた朝の続き","/ts-only-stats-into-cloudflare-d1","2026-10-06T00:00:00.000Z",{"title":291,"path":292,"publishedAt":293},"RSVP速読リーダーを自作した記録：画像2枚の企画からPhase 5まで、蔵書DBの本を1,500字/分で読めるか試す","/rsvp-reader-kickoff-phase3","2026-09-27T00:00:00.000Z",{"title":295,"path":296,"publishedAt":297},"SSOT（Single Source of Truth）とは — Codex に『初日からずれる』と指摘された統合コンテンツ計画","/what-is-ssot-and-day-one-divergence","2026-06-15T00:00:00.000Z",{"title":299,"path":300,"publishedAt":301},"蔵書957件の奥付情報を一括でDBに入れる抽出パイプラインの計画策定","/colophon-extraction-pipeline-plan","2026-05-10T00:00:00.000Z",{"title":303,"path":304,"publishedAt":305},"計算モジュールの動画化：Excel画面再現とマインドマップを分割画面にまとめるまで","/sim-modules-video-split-mindmap","2026-10-11T00:00:00.000Z","https://log.eurekapu.com/og/blog/stats-ssot-to-cloudflare-d1.png?v=2026-10-05T00%3A00%3A00.000Z&title=%E6%9C%88%E6%AC%A1%E7%B5%B1%E8%A8%88%E3%81%AESSOT%E3%82%92Git%E3%81%AETS%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%8B%E3%82%89Cloudflare%20D1%E3%81%B8%E7%A7%BB%E3%81%99%EF%BC%9ATurso%E6%8E%A8%E5%A5%A8%E3%82%92%E9%81%8E%E5%8E%BB%E3%81%AE%E6%A4%9C%E8%A8%8E%E8%B3%87%E6%96%99%E3%81%A7%E6%94%B9%E3%82%81%E3%81%9F%E6%97%A5&author=Kei%20Komatsu&sig=2c5c3329f8dd7aa8",1791766732892]