TigerBeetle:世界上最快的會計資料庫

banq發表於2024-02-22


TigerBeetle 是一個財務會計資料庫,專為關鍵任務安全性和效能而設計,為金融服務的未來提供動力。

希望讓其他人能夠輕鬆構建下一代金融服務和應用程式,而無需從頭開始拼湊會計或分類帳記錄系統。
TigerBeetle 採用最新的研究和技術來提供前所未有的安全性、耐用性和效能,同時將運營成本降低幾個數量級並提供出色的開發人員體驗。

安全
TigerBeetle 的設計遵循比 MySQL 等通用關聯式資料庫或 Redis 等記憶體資料庫更高的安全標準:

  • 嚴格的一致性、CRC 和碰撞安全還不夠。
  • TigerBeetle 可處理和恢復潛伏扇區錯誤,檢測並修復韌體讀/寫錯誤扇區的磁碟損壞或錯誤 I/O,檢測並修復資料篡改。
  • 利用雜湊鏈加密校驗和檢測和修復資料篡改(在少數群集上,如同非拜占庭損壞)。
  • TigerBeetle在設計上使用直接 I/O來避免EIO fsync 錯誤後核心頁面快取中的快取一致性錯誤。
  • TigerBeetle超過了單個磁碟和單個伺服器硬體的 fsync 永續性,因為磁碟韌體可能包含錯誤並且單個伺服器系統發生故障。
  • TigerBeetle作為複製狀態機和 TigerBeetle 伺服器叢集(稱為副本)提供嚴格的可序列性(一致性的黃金標準),以實現最佳的高可用性和分散式容錯。
  • TigerBeetle使用開創性的Viewstamped Replication和共識協議對一定數量的備份 TigerBeetle 伺服器執行同步複製,以實現低延遲自動領導者選舉,並消除與臨時手動故障轉移系統相關的腦裂風險。
  • TigerBeetle 具有“故障感知”能力,可以在全球共識協議的背景下從本地儲存故障中恢復,比 ZooKeeper 和 LogCabin 等複製狀態機提供更高的安全性。
  • TigerBeetle 不依賴於同步系統時鐘,不使用領導者租約,並執行基於領導者的時間戳,以便您的應用程式只能處理與傳輸超時有關的安全相對時間量。
  • 為了確保領導者的時鐘在“真實時間”的安全範圍內,TigerBeetle 組合了叢集中的所有時鐘來建立一個容錯時鐘,我們稱之為“叢集時間”。

效能
TigerBeetle 提供比 MySQL 等通用關聯式資料庫或 Redis 等記憶體資料庫更高的效能:

  • TigerBeetle使用小型、簡單的固定大小資料結構(賬戶和轉賬)和嚴格範圍的域。
  • TigerBeetle執行資料庫中的所有餘額跟蹤邏輯。“會計”業務邏輯內建於 TigerBeetle 中,以便您可以保持應用程式層簡單且完全無狀態。
  • TigerBeetle在設計上支援批處理。
  • 一切都是一批。TigerBeetle 能夠透過消除小批次引起的排隊延遲成本來分攤 I/O 成本,以實現更低的延遲,即使對於相當大的批次也是如此。
  • 如果你的系統沒有負載,TigerBeetle還會最佳化小批次的延遲。從核心的 TCP 接收緩衝區複製後(TigerBeetle 不執行使用者空間 TCP),TigerBeetle執行從網路協議到磁碟,然後到狀態機並返回的零複製直接 I/O ,以減少記憶體壓力和 L1-L3快取汙染。
  • TigerBeetle使用 io_uring 進行零系統呼叫網路和儲存 I/O。對於幾千次傳輸,系統呼叫在上下文切換方面的成本會迅速增加。
  • TigerBeetle透過使用固定大小的資料結構進行零反序列化,這些資料結構針對快取行對齊進行了最佳化,以最大限度地減少 L1-L3 快取未命中。
  • TigerBeetle利用 Heidi Howard 的靈活仲裁來減少同步複製到最多一個(或兩個)遠端副本(除了領導者之外)的成本,並在其餘跟隨者之間進行非同步複製。這提高了寫入可用性,而不會犧牲嚴格的可序列性或永續性。這還可以將伺服器部署成本降低多達 20%,因為具有靈活仲裁的 4 節點叢集現在可以f=2為複製仲裁提供與 5 節點叢集相同的保證。
  • TigerBeetle繞過短暫的灰色故障延遲峰值。例如,如果由於磁碟緩慢故障而通常需要 4 毫秒的磁碟寫入開始需要 4 秒,TigerBeetle 將使用叢集冗餘自動掩蓋灰色故障,而使用者不會看到任何 4 秒的延遲峰值。這是文獻中一種相對較新的效能技術,稱為“尾部容差”。

TigerBeetle 為您完成所有賬本驗證、餘額跟蹤、永續性和複製;您所要做的就是使用 TigerBeetle 客戶端來:

  1. 向 TigerBeetle 傳送一批准備(在單個網路請求中),然後
  2. 向 TigerBeetle 傳送一批提交(在單個網路請求中)。

架構
理論上,TigerBeetle 是一個複製狀態機,它採用初始啟動狀態(賬戶開戶餘額),並按確定性順序應用一組輸入事件(轉賬),在首先安全地複製這些輸入事件後,到達最終狀態(賬戶期末餘額)。

在實踐中,TigerBeetle基於LMAX架構 並做了一些改進。

我們採取相同的三個經典 LMAX 步驟:

  1. 將傳入事件安全地記錄到磁碟,並複製到備份節點,然後
  2. 將這些事件應用到記憶體狀態,然後
  3. 向客戶端確認

然後我們引入一些新內容:

  1. 完全刪除本地日誌步驟,並且
  2. 將其替換為並行複製到 3/5 分散式副本。

然後我們的架構就變成了三個簡單的步驟:

  1. 將傳入事件安全地複製到分散式副本的法定數量,然後
  2. 將這些事件應用到記憶體狀態,然後
  3. 向客戶端確認

這就是 TigerBeetle 如何消除領導者本地磁碟中的灰色故障,以及 TigerBeetle 如何消除複製節點的網路鏈路中的灰色故障。

與 LMAX 一樣,TigerBeetle 使用每核執行緒設計來實現最佳效能,並透過嚴格的單執行緒來強制執行單寫入器原則並避免多執行緒協調訪問資料的成本。

資料結構
為了效能和簡單性,所有資料結構都是固定大小的,並且有兩種主要的資料結構:事件和狀態。

1、事件Event活動
事件是不可變的資料結構,可以例項化或改變狀態資料結構:

  • 事件不能改變,即使是其他事件也不能改變。
  • 事件無法匯出,因此必須在執行之前記錄下來。
  • 事件必須按確定的順序一個接一個地執行,以確保可重玩性。
  • 事件可能取決於過去的事件(如果他們選擇)。
  • 事件不能取決於未來的事件。
  • 事件可能取決於處於確切版本的狀態(如果他們選擇)。
  • 事件可能成功或失敗,但事件的結果永遠不會儲存在事件中;它儲存在由事件例項化或變異的狀態中。
  • 事件只能有一個不可變的版本,可以透過事件的 id 直接引用。
  • 應保留事件以供審計之用。然而,一旦在狀態快照中捕獲事件的影響,事件可能會被排入單獨的冷儲存系統,以壓縮日誌並縮短啟動時間。

create_transfer:建立帳戶之間的轉賬(對映到“準備”)。我們按大小降序對欄位進行分組,以避免 C 實現中不必要的結構填充。

 

        create_transfer {
                      id: 16 bytes (128-bit)
        debit_account_id: 16 bytes (128-bit)
       credit_account_id: 16 bytes (128-bit)
                  amount: 16 bytes (128-bit) [required, an unsigned integer in the unit of value of the debit and credit accounts, which must be the same for both accounts]
              pending_id: 16 bytes (128-bit) [optional, required to post or void an existing but pending transfer]
           user_data_128: 16 bytes (128-bit) [optional, e.g. opaque third-party identifier to link this transfer (many-to-one) to an external entity]
            user_data_64:  8 bytes ( 64-bit) [optional, e.g. opaque third-party identifier to link this transfer (many-to-one) to an external entity]
            user_data_32:  4 bytes ( 32-bit) [optional, e.g. opaque third-party identifier to link this transfer (many-to-one) to an external entity]
                 timeout:  4 bytes ( 32-bit) [optional, required only for a pending transfer, a quantity of time, i.e. an offset in seconds from timestamp]
                  ledger:  4 bytes ( 32-bit) [required, to enforce isolation by ensuring that all transfers are between accounts of the same ledger]
                    code:  2 bytes ( 16-bit) [required, an opaque chart of accounts code describing the reason for the transfer, e.g. deposit, settlement]
                   flags:  2 bytes ( 16-bit) [optional, to modify the usage of the reserved field and for future feature expansion]
               timestamp:  8 bytes ( 64-bit) [reserved, assigned by the leader before journalling]
} = 128 bytes (2 CPU cache lines)


create_account:建立賬戶。

  • 我們使用貸方和借方而不是 "credit 應付 "或 "應收debit",因為貸方餘額的含義取決於賬戶是資產、負債還是權益、收入還是支出。
  • posted過賬金額指透過轉賬過賬的金額。
  • pending掛賬金額指的是尚未透過兩階段轉賬掛賬的賬內金額,即轉賬仍處於掛賬狀態,轉賬超時尚未觸發。換句話說,轉賬金額已保留在待轉賬賬戶餘額中(以避免重複支出),但尚未記入已過賬餘額。如果轉賬最終失敗,預留金額將回滾。預設情況下,轉賬會自動過賬,但將金額保留為待處理,然後再過賬有時會很方便,例如在轉換信用卡付款時。
  • 賬戶的借方餘額由 debits_posted 加上 debits_pending 得出,同樣,賬戶的貸方餘額也由 debits_posted 加上 debits_pending 得出。
  • 用借方餘額減去貸方餘額,即可得出賬戶的總餘額。
  • 我們將分類賬的兩邊(借方和貸方)分開,以避免處理帶符號的數字,並保留更多有關賬戶性質的資訊。例如,兩個賬戶的餘額可能都是 0,但其中一個賬戶的賬面餘額可能都是 1,000,000 單位,而另一個賬戶的賬面餘額可能都是 1 單位,兩者的餘額都是 0。
  • 賬戶一經建立,只能透過轉賬事件進行更改,以保持不可更改的書面記錄,便於審計。

     

      create_account {
                      id: 16 bytes (128-bit)
          debits_pending: 16 bytes (128-bit)
           debits_posted: 16 bytes (128-bit)
         credits_pending: 16 bytes (128-bit)
          credits_posted: 16 bytes (128-bit)                      
           user_data_128: 16 bytes (128-bit) [optional, opaque third-party identifier to link this account (many-to-one) to an external entity]
            user_data_64:  8 bytes ( 64-bit) [optional, opaque third-party identifier to link this account (many-to-one) to an external entity]
            user_data_32:  4 bytes ( 32-bit) [optional, opaque third-party identifier to link this account (many-to-one) to an external entity]                      
                reserved:  4 bytes ( 32-bit) [reserved for future accounting policy primitives]
                  ledger:  4 bytes ( 32-bit) [required, to enforce isolation by ensuring that all transfers are between accounts of the same ledger]
                    code:  2 bytes ( 16-bit) [required, an opaque chart of accounts code describing the reason for the transfer, e.g. deposit, settlement]
                   flags:  2 bytes ( 16-bit) [optional, net balance limits: e.g. debits_must_not_exceed_credits or credits_must_not_exceed_debits]
               timestamp:  8 bytes ( 64-bit) [reserved]
} = 128 bytes (2 CPU cache lines)

2、狀態
狀態是捕捉事件結果的資料結構:

  • 狀態總是可以透過重放所有事件得到。

TigerBeetle 恰好提供了一種狀態資料結構:
  • Account賬戶:一個顯示所有傳輸效果的賬戶。

為了簡化、減少記憶體複製並儘可能重用事件資料結構的線格式,我們重用 create_account 事件資料結構來例項化相應的狀態資料結構。


深入的討論

  • - OLTP和OLAP工作負載
  • - 為什麼SQL不是最好的資料格式語言?
  • - 當涉及到高規模事務處理時,傳統OLGP資料庫存在什麼問題?
  • - 為什麼記憶體會成為擁塞瓶頸?
  • - TigerBeetle的安全性和效能特點。
  • - LSM,它的挑戰以及TigerBeetle如何確保可預測的效能。
  • - 128位元組如何足以使用財務會計建模和表示任何型別的業務? 

相關文章