第一週講完了架構、成本、資料庫、資料模型、定價——全部都是「決定」。決定這種東西,當下看不出對錯,要跑一段時間才知道。
今天不講新東西,來盤點。而且盤點要有誠意:只列做對的,那叫自誇不叫回顧。 所以下面三個做對的取捨之後,會有一個我到現在還後悔的決定。
Day 2 講的兩層分離。具體切法是:
真正讓我覺得這個決定做對的,是一個當初沒想清楚的副作用:國內那台採集機不對外提供服務(它只把結果推到 R2 和 D1),所以它不需要網域、不需要 ICP 備案。
在啟動階段,省下的不是錢,是時間。只要你的服務不對外提供,整個前置流程就少掉一整套行政手續。
另一條是「不翻牆」:國內來源用國內機器採,海外來源(例如美國 SEC 那類)用海外機器採。海外機器本來就在牆外,天然不需要代理工具。而國內伺服器裝代理工具是違規的,而且會封號——這條線一旦踩過去,賠掉的是整個帳號,不是一次採集。
成本也很實在:國內輕量伺服器大概 50 到 100 元/月(人民幣)。這是我整條採集線唯一的固定支出。
Day 4 講的。當時的誘惑是「都上線了,要不要換 PostgreSQL」。
沒換的理由很簡單:採集端的瓶頸在網路 IO——下載 PDF、跟來源端的流量限制打交道——不在資料庫寫入。A 股、日本、韓國三個採集程式各自獨立行程並行跑,寫同一個 SQLite;WAL 模式下讀寫不會互相鎖住整庫,多行程並行寫完全沒壓力。
什麼時候才真的需要 PostgreSQL?寫入頻率到每秒幾百上千次。財報採集到不了那個量級,而且這個領域的資料量是「每天幾千篇」,不是「每秒幾千筆」。
不後悔的理由還有一個:一個檔案就是一個資料庫。 備份是複製檔案,要搬到 D1 也是原樣搬(D1 就是 SQLite,資料表結構不用改)。
Day 5 的資料模型。每一份文件的主鍵不是自己產的流水號,是來源端的公告 ID;正文不進資料庫,存成檔案,由 content_path 指向 R2 上的位置。
這個決定後來救了命。
解析器每次升版,幾十萬篇要重轉一輪(Day 15 會細講 PARSER_VERSION 的機制)。重轉就是把同一批文件重新跑一次解析、再寫回去。因為衝突鍵是公告 ID,重跑不會產生第二份,只會覆蓋同一筆。
如果當初用流水號當主鍵,重轉一次就是資料翻倍;重轉兩次就是三倍;而且沒有任何欄位可以拿來判斷哪一份才是新的。這個洞一旦踩下去,補救成本遠高於當初多想五分鐘。
前面三個是取捨,這一個是漏洞。
push.py 從第一天就是單向的:把本地新增的、改過的記錄 upsert 到 D1,把正文檔案複製到 R2。邏輯上完全說得通——資料不是只會變多嗎?
不是。
2026-09 清理台灣那批壞正文(華康字型抽不出字的那批,約 1,690 篇)的時候才發現:本地刪掉的東西,線上那一份永遠留著,網站照樣把它發給使用者。
清理等於沒做。而且整個過程無聲無息——push 只會回報「這一輪推了 N 筆」,沒有任何異常,因為從 push 的角度看,它每一輪都完美達成了任務。
補救方式是加一張刪除墓碑表(deleted_docs):delete_document() 刪記錄時自動留墓碑(公告 ID + 正文路徑),push 每次推送前先回放墓碑——D1 按公告 ID 批次刪、R2 用 rclone delete --files-from 按精確路徑刪,刪完才清墓碑。
同一個盲點還有第二個後果:當某個來源整批不能再對外提供的時候,只要推送端沒有明確把它排除掉,下一輪它就會原封不動長回來。所以「下架」要動的其實不只線上那一份。(Day 25 細講)
回頭看,三個做對的取捨有個共通點:它們都是在「先活下來」這個前提下做的。 不翻牆、不換資料庫、不多開服務——省下來的是維運時間,而一個人做這件事,維運時間就是全部的本錢。
至於那個後悔的決定,問題不在技術難度。加一張墓碑表、補一段回放邏輯,是半天的工。問題在預設:「資料只會變多」是我沒有檢查過就相信的假設,而它在整個第一週裡從來沒有被寫下來過。
沒有被寫下來的假設,就是未來的技術債。
下週開始進入這個系列真正的核心:PDF 解析。明天先給一張地圖——財報 PDF 的四個坑,以及每一個坑到底難在哪裡。
本文為 2026 iThome 鐵人賽連載第 7 篇。系列主題:把東亞財報 PDF 煉成 LLM 讀得懂的 Markdown。