iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
Claude AI

Claude Code陪跑:一人開發者的訂閱制SaaS架構與十大地雷實戰記系列 第 13

# Day 13|地雷#10:逐筆查DB燒穿雲端額度,closure 快取怎麼救

  • 分享至 

  • xImage
  •  

事故現場

雲端資料庫服務商寄來用量警告:資料傳輸量暴增,額度快燒完了。服務功能一切正常、沒有任何錯誤、使用者毫無感覺——但帳單背後,某段程式碼正在以驚人的頻率重複做同一件事。

根因解剖

凶手是一支週期性執行的通知排程,它的工作是逐一檢查每個使用者關注的項目、決定要不要發通知。第一版的寫法:

def run_alert_cycle():
    for user in get_all_subscribed_users():
        for item in get_user_items(user):
            name = fetch_item_name(item.id)        # ← 每個項目查一次 DB
            threshold = fetch_thresholds(item.id)  # ← 又查一次
            ...

問題的形狀:迴圈裡查的資料,其實大部分是跨使用者共用的。 項目名稱、門檻設定這些東西,一個週期裡查一次就夠了,但這段程式碼讓每個使用者的每個項目都各查一次。100 個使用者 × 平均 10 個項目 × 每 30 秒一輪 = 每小時十幾萬次本可省略的查詢。

它在開發環境完全隱形:本機資料庫(Day 2)查詢零成本、測試資料只有幾個使用者。這是一種只有在「真實規模 × 按量計費」同時成立時才會現形的 bug——而那正好就是正式站的定義。

防線怎麼蓋

修法是把「批次共用的值」提出迴圈,抓一次、關進 closure 共用:

def make_alert_checker():
    """工廠函式:把整輪共用的資料抓一次,關進 closure。
    禁止在迴圈裡逐筆查 DB——曾燒穿雲端傳輸額度(地雷清單 #10)。"""
    names = fetch_all_item_names()        # 一次撈全表
    thresholds = fetch_all_thresholds()   # 一次撈全表

    def check(user, item):
        name = names[item.id]             # 迴圈裡只讀記憶體
        ...
    return check

def run_alert_cycle():
    check = make_alert_checker()
    for user in get_all_subscribed_users():
        for item in get_user_items(user):
            check(user, item)

一輪的查詢次數從「使用者數 × 項目數 × 2」降到常數 2。用 closure 而不是全域快取的理由:生命週期綁定在一輪執行,每輪開始時自然拿到新資料,不需要另外管快取失效。

這條雷的後續:形狀會復發

最有意思的是後日談。幾個月後開發另一個新功能,一段新程式碼又長出了一模一樣的形狀——迴圈裡逐筆查共用資料。這次在 code review 階段(由讀過地雷清單的 agent 標記出來)就被攔下了。

這件事讓我對地雷清單的價值有了更立體的理解:地雷不是一個位置,是一種形狀。 記「那段程式碼有問題」只能防那段程式碼;記「『對每個使用者做某事』的迴圈裡藏著重複查詢的形狀」才能防所有還沒寫出來的複發。清單條目要寫到「形狀」這個抽象層級,它的防護才是面而不是點。

十條地雷的收尾

到今天,十條地雷講完了。回頭看它們的共同點:沒有一條是「不會寫程式」造成的,每一條都是「不知道這個特定系統的特定事實」造成的。 方言差異、單位約定、時區窗口、API 黑盒、舊版本包袱——這些知識不在任何教科書裡,只在這個系統的事故史裡。這就是為什麼 Day 3 那份文件是整個一人維運體系的地基。

明天開始換主題:訂閱分級系統的設計——一個商業服務跟純技術專案分道揚鑣的起點。


上一篇
# Day 12|地雷#9:一個布林旗標,擋住了多階訂閱等級的擴充
下一篇
# Day 14|訂閱分級系統設計:等級不只是 on/off
系列文
Claude Code陪跑:一人開發者的訂閱制SaaS架構與十大地雷實戰記15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言