Vault3v3.0.0

效能設計Performance

交易不做任何 I/O

一筆交易只把帳戶標記為 dirty 然後立刻返回。寫入是非同步的、有去抖動的, 而且只寫變動過的餘額,不是整張表。

200 人的伺服器上,一次商店購買原本代表主執行緒要等一次 200 列的 batch upsert; 現在是一列,而且在別的執行緒。

每筆交易寫入的列數    200 → 1
每筆交易的主執行緒 I/O  阻塞寫入 + 等待網路 → 無
效能對比

交易具原子性

withdrawPlayerdepositPlayer 是對帳戶的單一原子操作, 以 ConcurrentHashMap.compute() 實作 —— 讀取、餘額檢查、寫入在同一把鎖內完成。

兩個插件同時操作同一個帳戶(例如商店與簽到獎勵)不會互相覆寫。 8 執行緒 × 25,000 筆並行扣款的壓力測試下,最終餘額精確無誤差。

名稱解析不掃描目錄

以玩家名稱查詢時,解析順序由便宜到昂貴:

  1. 線上玩家精確比對
  2. 本地 name→UUID 快取,玩家加入時寫入,能正確處理改名
  3. 伺服器 usercache(getOfflinePlayerIfCached,以反射呼叫以相容舊版 API)
  4. 完整目錄掃描 —— 每 60 秒最多一次,且結果會回填快取

失敗查詢有 60 秒負快取,不存在的名字不會反覆觸發掃描。 實測 210,000 次查詢僅觸發 1 次目錄掃描。

這條路徑會被 %vault_balance_<player>% 這類變數以及舊版 String 形式的 Vault Economy API 觸發 —— 兩者都可能每秒被呼叫數十次。

調校

唯一需要權衡的是 storage.transaction_flush_delay_ticks: 越低則當機時損失的交易越少,但資料庫往返越頻繁。預設 20 tick(一秒)在多數情況下是合理的。

Transactions perform no I/O

A transaction marks the account dirty and returns immediately. Writes happen asynchronously, debounced, and cover only the balances that changed — never the whole table.

On a 200-player server a shop purchase used to mean the main thread waiting on a 200-row batch upsert. Now it is a single row, off-thread.

Rows written per transaction   200 → 1
Main-thread I/O per transaction  blocking write + network wait → none
Performance comparison

Transactions are atomic

withdrawPlayer and depositPlayer are single atomic operations on the account, implemented with ConcurrentHashMap.compute() so the read, the sufficient-funds check and the write all happen under the same lock.

Two plugins touching the same balance at once — a shop and a daily-reward task, say — cannot overwrite each other. Under a stress test of 8 threads × 25,000 concurrent withdrawals the final balance is exact.

Name lookups do not scan the data directory

Resolving a player name goes from cheapest to most expensive:

  1. Exact match against online players
  2. Local name→UUID cache, populated on join so renames stay correct
  3. The server's own user cache (getOfflinePlayerIfCached, called reflectively so older APIs still work)
  4. A full directory scan — at most once every 60 seconds, warming the cache as it goes

Failed lookups are negatively cached for 60 seconds, so a name that does not exist cannot trigger repeated scans. In testing, 210,000 lookups triggered a single directory scan.

This path is reached by placeholders such as %vault_balance_<player>% and by the legacy String form of the Vault Economy API — both of which can run dozens of times per second.

Tuning

The only real trade-off is storage.transaction_flush_delay_ticks: lower loses fewer transactions in a crash but costs more database round trips. The default of 20 ticks (one second) suits most servers.

Vault3 · 以 AGPL-3.0 散布,內含 LGPL-3.0 的 VaultAPI。
Vault3 · Distributed under AGPL-3.0. Bundles VaultAPI under LGPL-3.0.
Copyright © 2021-2026 CloudXact Studio. All Rights Reserved.