Skip to main content
交易場所對一個帳戶要知道的事,有些沒辦法在區塊執行期間算出來。成交量級距取決於三十天的交易;獎勵取決於一個還沒關閉的窗口;推薦歸因取決於幾個月前建立的關係。 把這類工作放進區塊執行,會在兩個方向上出錯:一是每個區塊都得為一項幾乎沒有區塊用得到的計算付出代價,二是內核得攜帶它本來沒有理由持有的歷史資料。 程式服務(program services)把方向反轉過來解決這個問題:計算在區塊之外跑,依據的是已提交的紀錄;結果再作為協議狀態提交回鏈上,執行過程就能像讀其他任何配置一樣,以常數時間讀到。
已提交的交易歷史
程式服務窗口計算,在區塊之外從鏈上即時讀取手續費表
自上次套用以來是否變化?
不寫入
協議交易
鏈上狀態
內核在執行時讀取常數時間
比對的是費率,不是級距位置門檻移動,或某一級被重新定價,都會改變帳戶實際付的錢,卻不改變它的級距序號。
最終費率由鏈來解析每一批都提交成功之後,該週期才記錄為已套用;中途當機會重放整個週期,這是安全的,因為計算是冪等的。
鏈仍然是權威。服務並不持有網路依賴的狀態,只是提議一個值;只有鏈接受了的才算數。

目前在運行的

以成交量為準的費率級距。 這個服務消費節點串流推送的交易歷史,累計各帳戶的成交量,並按排程做快照。一個週期結束,就計算每個帳戶在滾動窗口內的成交量,透過從鏈上即時讀取的手續費配置映射成級距,再分批把有變動的帳戶寫回鏈上。 這句話裡有幾個細節是承重的:
  • 級距表從鏈上讀取,絕不寫死。 服務若自己留一份副本,網路改了費率表之後,它還會繼續套用昨天那一張。
  • 比對的是費率,不是級距序號。 只比級距序號,會漏掉兩種真實情況:門檻移動了,成交量沒變的帳戶卻落進不同級距;某個級距重新定價了,序號卻沒變。兩者都會改變帳戶實際付的錢,卻都不會改變序號。
  • 最終費率由鏈來解析。 交易攜帶的是級距序號,執行過程對照鏈上即時的手續費配置把它解析出來。級距序號超出有效範圍,整批交易失敗,不會部分套用。
  • 每一批都提交成功之後,該週期才記錄為已套用。 週期中途當機會重放整個週期,這樣做是安全的,因為計算是冪等的:同一個窗口產生同樣的結果。

失效模型

這些服務夾在兩個系統中間,而兩邊都會時不時不可用。設計一開始就假定了這一點,不把它當異常處理。 相依項目失效——資料庫、節點的資料流、節點的 API——會以退避策略重試,不會終止行程;重新啟動修不好一個連不上的相依項目,只會在故障之上再加一次冷啟動。仍然算致命的,是重新啟動修得好、或者必須讓維運人員看到的問題:啟動時配置無效、無法綁定健康檢查端點,以及 panic。 故障期間,行程保持運行、把自己回報為 not-ready,並累計錯誤計數。所以維運訊號是「not-ready 已經多少分鐘了」,不是「行程還活著嗎」;前者才是有用的問題,因為行程活著、卻已經一小時擷取不到資料,那才是真正的事故。 收到編排系統發出的訊號,行程會優雅關閉:停止工作、寫出檢查點、乾淨退出。少了這一步,每一次例行部署都要賠上一個還沒寫出的窗口和一次重放。
週期算完了但還沒套用,不等於這個週期丟了。計算是冪等的,而且要等寫入成功才會記錄週期已套用,所以一次運行被中斷,恢復的方式是重做那個窗口,不是跳過去。

這個模式為何可以推廣

寫回路徑是通用的。協議裡本來就有設定帳戶級配置和設定全域配置的交易,而所謂程式服務,就是任何一個從已提交歷史中替這些交易算出一個值的行程。 費率級距是目前唯一在運行的。激勵計畫、推薦歸因和活動資格的形態一樣:對交易歷史做窗口計算,跟當前已套用的值比對差異,再分批寫回。它們該放在這裡而不是內核裡,理由和費率級距一樣——計算是週期性、面向歷史的,而執行需要的答案必須是常數時間的查表。這些都還沒有建構;可以推廣的是這個模式,不是「它們一定會採用這個模式」的承諾。 這些計畫的商業條款在費率與計畫中說明。本頁講的是結果如何上鏈。

後續閱讀

索引器

這些服務所消費的資料流。

手續費

商業面:有哪些級距,各自的費率是多少。

IntentionKernel

寫回的配置在執行期間怎麼讀。

狀態模型

配置鍵為何帶版本,以及用戶端為何應當即時解析它們。