帖子
分享您的知识。
Sui Move 中共享对象的设计模式和并发管理
在 Sui Move 中处理共享对象时,我正在尝试采用正确的架构模式,尤其是当多个用户需要同时与它们进行交互时(例如,在游戏竞技场、DAO 投票跟踪器或借贷金库中).
我读过共享对象可能会引入同步问题,但我仍然不清楚:
- 我究竟应该在什么时候使用
shared
物体与owned
? - Sui 如何处理同一个共享对象上的并发交易尝试——我需要手动排队/锁定东西吗?
- 设计支持高并发性但避免频繁
ConflictTransaction
错误的模块的最佳做法是什么? - 我应该做一些特别的事情(比如将可变字段拆分成子对象)来减少争用吗?
- Sui
- Architecture
- NFT Ecosystem
- Move
答案
13当你在 Sui 上构建 DApp 时,如果多个用户需要与同一个状态进行交互,例如在游戏竞技场、借贷协议或 DAO 中,你应该使用共享对象而不是拥有的对象. 共享对象是可公开访问和可变的,这意味着只要它们在全局对象图中,任何人都可以对其调用入口函数. 相比之下,自有物品对用户来说是私有的,非常适合存放钱包或库存等个人资产. 因此,当状态更新必须可供许多用户同时查看和使用时,您应该使用共享对象.
Sui 通过一种名为因果顺序验证的机制处理共享对象的并发事务,该机制要求您明确提供共享对象的最新已知版本. 如果两个用户尝试使用过时的版本同时修改同一个共享对象,则其中一个将成功,而其他用户则因ConflictTransaction
错误而失败. Sui 不会在本地排队或锁定共享对象,因此您需要设计 Move 模块以最大限度地减少重叠写入. 减少冲突的最佳方法之一是针对并行性进行设计. 例如,您可以将高争用字段拆分为单独的子对象,而不是将所有可变状态存储在根共享对象中. 然后,这些孩子中的每一个都可以独立编写,从而减少了发生交易冲突的可能性.
一种很好的做法是将主共享对象保留为目录,并将经常更新的精细状态(例如玩家分数、选票或贷款余额)移动到动态字段或较小的子对象中. 这样,用户只更新他们需要的特定部分,而不是整个共享对象. 将读取密集型和写入密集型组件分开也是明智之举. 此外,您应该建立清晰而确定性的事务流程,始终使用最新版本的共享对象和重试逻辑,让客户端处理失败的交易.
您可以在此处的 Sui 文档中了解有关共享对象设计和冲突缓解的更多信息:https://docs.sui.io/concepts/objects#shared-objects.
如果你管理的是投票追踪器之类的东西,下面是一个简化的交易区块示例,以减少冲突:
const tx = new TransactionBlock();
tx.moveCall({
target: `$ {packageID}:: vote_tracker:: cast_vote`,
论点:[
tx.sharedObject (VoteTrackerId),//共享对象引用
tx.pure (voterID),//拥有的对象或公钥
tx.pure (ProposAlid),//纯数据
tx.pure(VoteChoice)
]
});
在 Sui Move 中,共享对象(标key + store
有)对于多用户交互至关重要,但需要仔细的并发性设计. 当状态必须是全局可变的(例如游戏、DAO)时使用它们,而自有/不可变对象适合单用户场景. Sui 会自动序列化每个共享对象的事务,但频繁的写入可能会触发ConflictTransaction
错误——通过将热字段拆分为单独的子对象(例如,每个用户一个)或批量更新来缓解这种情况. 要实现高吞吐量,请采用事件来源模式:将操作存储为不可变事件并定期聚合状态. 始终尽量减少可变共享字段,并优先通过transfer::share_object
直接突变进行原子交换.
在 Sui Move 中,有效处理共享对象和并发性需要精心设计,以避免竞争条件和高争用. 以下是最佳实践和架构模式的细分:
1. 何时使用共享对象与自有对象:
*共享对象:当多个用户需要同时与同一个对象进行交互时使用(例如,共享游戏状态、DAO 投票跟踪器). *拥有的对象:当对象特定于单个用户或账户时使用,可避免共享状态并减少并发问题.
2. Sui 中的并发交易处理:
- Sui 使用交易锁定自动处理并发性,以防止同时修改同一个对象.
- 你不需要手动排队或锁定共享对象,因为 Sui 会在对象级别强制锁定. 但是,如果多个事务在同一个对象上发生冲突,您可能会出现冲突交易错误.
3. 高并发的最佳实践:
*尽量减少内容:将可变字段拆分为单独的子对象,以允许对对象的不同部分进行并行更新. *避开热点:将经常修改的状态分配给多个对象(例如,与其共享账本,不如将其拆分为多个存储桶或账户). *版本控制:使用版本控制对象或创建新对象来跟踪更新,而不是重复修改同一个对象.
4. 管理争用:
- 考虑将可变字段拆分为较小的对象以减少争用. 这允许同时更新对象的不同部分,从而减少发生冲突的机会.
- 使用原子事务将相关操作捆绑成一个,确保相关对象之间的一致性.
示例:
如果你有 DAO 的投票追踪器:
- 与其将所有选票存储在一个共享对象中,不如将其拆分为多个对象(例如,每个提案一个),允许对每个提案进行并行更新,同时减少争用.
结论:
为了实现高并发性,通过分配状态、使用子对象和利用 Sui 的自动事务处理来最大限度地减少争用. 确保您了解访问模式并构造对象以最大限度地减少冲突点.
了解 Sui Move 中共享对象的设计模式和并发管理
在Sui Move中处理共享对象时,管理并发性变得至关重要,因为多个用户可能需要同时与同一个对象进行交互. 对于诸如游戏竞技场、DAO 投票跟踪器和借贷金库之类的用例尤其如此,在这些用例中,预计会出现高并发性. Sui 网络提供了一些功能来帮助管理并发性,但仍需要精心设计以避免常见的陷阱,例如ConflictTransaction错误.
1. 何时使用共享对象与拥有的对象
*拥有的物品:
-
如果对象由单个地址管理,则该对象为所有权,该地址可以传输或修改该对象. *当你期望一个实体控制一个对象并与之交互时,使用自有对象. 例如,如果您有一个装有硬币的钱包,则每个钱包都拥有自己的硬币的所有权. *共享对象:
-
多个地址可以同时访问和修改共享对象. Sui 允许您将对象定义为共享,方法是使它们可变并为多方提供访问权限. *使用共享对象适用于多个用户或地址需要与同一个对象进行交互的场景,例如:
*DAO 投票追踪器:多个用户可以对同一个对象进行投票. *借出金库:多个用户可以从同一个保管库借款. *游戏竞技场:多个用户同时与游戏状态进行交互.
主要注意事项:应谨慎使用共享对象,因为它们会引入争议,即多个事务试图同时访问和修改同一个对象的情况.
2. 共享对象上的并发和交易尝试
Sui 通过强制执行事务隔离来确保正确处理同一 “共享对象” 上的并发交易. 如果多个事务尝试同时改变同一个共享对象,其中一个交易将由于争议而失败.
当一个事务尝试修改已被另一个事务修改的共享对象时,Sui 将返回一个ConflictTransaction错误. 但是,Sui 在内部处理大部分并发管理,您无需手动排队或锁定对象. 相反,您需要设计合约以最大限度地减少争用,并避免两笔交易经常发生冲突的情况.
3. 高并发和减少冲突交易错误的最佳实践
要设计能够处理高并发性并避免频繁出现ConflictTransaction错误的模块,请遵循以下最佳实践:
一个. 尽量减少争用点:
*限制可变访问:尽可能减少共享对象中可变字段的数量. 每个可变字段都会引入潜在的争论点. *使用自有对象获取独立数据:与其共享所有内容,不如将独立数据存储在自有对象中. 例如,如果您有用户特定的数据(例如余额),请将其存储在自有对象中,而不是共享对象中.
b. 使用 “状态分区” 模式:
-
将您的共享对象拆分为多个子对象以减少争用. 例如,你可以将它拆分成几个子对象,而不是让一个共享对象来跟踪整个游戏状态:
-
每个玩家的状态一个.
-
一个用于游戏设置.
-
一个用于排行榜数据.
-
这样,与系统不同部分相关的事务可以同时发生,而不会相互阻塞.
c. 批量操作:
- 处理共享对象时,批量更新以减少需要与对象交互的事务数量. 这有助于避免频繁的冲突. *使用向量或集合等集合在单个事务中管理多个更新.
d. 使用原子操作:
- 尽可能将模块设计为尽可能使用原子操作. 例如,与其使用单独的事务来检查条件然后更新字段,不如将检查和更新捆绑到一个原子操作中.
e. 重试逻辑:
- 虽然 Sui 在内部处理重试,但您可以在应用程序级别实现重试逻辑以处理偶尔的争用错误. 例如,如果
ConflictTransaction
发生错误,您可以简单地重试该事务或将其排队以备日后执行.
4. 通过将可变字段拆分为子对象来减少争用
这是强烈推荐的做法. 处理共享对象时,共享对象中有可变**的字段越多,两个事务尝试同时改变同一个字段的可能性就越大.
*示例 1:如果您有一个代表借出文件库的共享对象,请将其拆分为子对象:
- 一个用于存款:单独跟踪个人存款.
- 一个用于贷款:单独跟踪个人贷款.
- 一个用于保管库状态:跟踪一般状态信息(例如,总价值、利率).
这样,修改一个子对象(例如贷款子对象)的交易不会与修改另一个子对象(例如存款子对象)的交易发生冲突.
*示例 2:在游戏竞技场中:
- 将游戏状态拆分为每个玩家的子对象、游戏状态和战斗统计数据. 这降低了不同玩家与各自游戏状态互动时发生冲突的风险.
通过将可变字段拆分为子对象,可以降低多个用户尝试同时修改同一个对象的可能性.
5. 在 Sui 中管理并发的其他技术
*锁定(显式锁定):虽然 Sui 不要求您手动处理锁定,但在某些情况下,您可能需要使用锁定对象(确保一次只有一个事务可以访问特定资源的独立对象)强制执行显式锁定.
*时间隔离:如果您的对象只是偶尔发生变化,请考虑引入基于时间的隔离. 例如,如果您正在管理保管库或投票系统,请设置投票期或交易窗口以限制特定时间内的互动.
6. 管理子对象并发的示例代码
以下示例说明了如何通过将借出文件库拆分为较小的子对象来减少争用:
module LendingVault {
struct Deposit has store {
amount: u64,
depositor: address,
}
struct Loan has store {
amount: u64,
borrower: address,
}
struct VaultState has store {
total_value: u64,
interest_rate: u64,
}
struct LendingVault has store {
deposits: vector<Deposit>,
loans: vector<Loan>,
vault_state: VaultState,
}
// Function to add a deposit
public fun add_deposit(vault: &mut LendingVault, depositor: address, amount: u64) {
let deposit = Deposit { amount, depositor };
Vector::push_back(&mut vault.deposits, deposit);
vault.vault_state.total_value = vault.vault_state.total_value + amount;
}
// Function to add a loan
public fun add_loan(vault: &mut LendingVault, borrower: address, amount: u64) {
let loan = Loan { amount, borrower };
Vector::push_back(&mut vault.loans, loan);
vault.vault_state.total_value = vault.vault_state.total_value - amount;
}
}
摘要
*当多个用户需要同时与同一个对象(例如 DAO 投票、游戏状态、借出保管库)进行交互时,使用共享对象. *通过将大型共享对象拆分为较小的子对象并减少可变字段的数量来最大限度地减少内容. *遵循状态分区、批处理操作和使用原子操作等最佳实践,避免频繁出现 ConflictTransaction 错误. *重试逻辑和乐观并发控制可用于优雅地处理冲突.
- Sui 的交易模型和冲突解决非常强大,但共享对象的设计是确保高效并发访问的关键.
通过遵循这些设计原则,您将能够在基于 MOVE 的模块中管理高并发性,同时避免诸如冲突和交易错误之类的典型陷阱.
使用共享对象进行多作者访问(例如游戏、DAO). 关键模式:
-
自有与共享 -拥有:单一作家(更快).
-共享:并发写入(更高的 gas). -
并发处理 -Sui 自动对共享对象 TxS 进行序列(无需手动锁定).
-将热门数据拆分为子对象(例如,每个玩家 1 个)以减少冲突.
3.缓解冲突
-隔离高流量字段(例如,Table<ID, PlayerData>
).
-使用 PTB 进行原子多对象操作.
注意:
-ConflictTransaction
:拆分数据或批量更新.
-共享物体上的气体尖峰.
*(Sui 的运行时处理冲突——通过数据分片进行优化. ) *
transfer::share_object
当多个用户必须改变同一个对象(例如,保管库、DAO、游戏状态)时,使用共享对象(). 否则使用自有.
TransactionLockConflict
Sui 通过Narwhal & Tusk 共识处理并发问题:共享对象上的冲突交易被序列化——每轮只有一次成功;其他交易失败. 无需手动锁定,但必须为重试进行设计.
最佳实践:
-最大限度地减少共享对象的可变状态;将热点拆分为单独的子对象(例如,每个用户的保管库共享).
-使用VecMap
或Table
进行可扩展映射.
-避免在共享函数中长时间运行计算.
-发出事件而不是存储临时状态.
-对于高争用系统,使用批处理或提交-显示方案来减少冲突.
共享对象可以全局访问,但会产生更高的气体和争用——针对客户端代码中的糜等性和重试逻辑进行了优化.
对于 Sui Move 中的共享对象,使用以下模式来管理并发性:
####1. 共享与拥有?
-共享对象 (key + store
):
-用于全球状态(例如,游戏、DAO).
-允许并行写入(与 EVM 的序列化 Tx 不同).
struct GameArena has key, store { id: UID, players: vector<address> }
-拥有的物品: -用于用户特定数据(例如 NFT、钱包).
struct PlayerInventory has key { id: UID, items: vector<Item> }
####2. 并发处理
-无手动锁定:Sui自动解决冲突(与 EVM 不同).
-拆分内容:将共享对象分成较小的子对象(例如,每个玩家或每个分片).
struct PlayerScore has key, store { id: UID, points: u64 } // Less contention
####3. 减少ConflictTransaction
错误
-隔离写入:在不同的 TX 中更新不同的字段.
-&mut
明智地使用:避免广泛突变.
public entry fun update_score(
arena: &mut GameArena,
player: address,
points: u64
) { /* ... */ } // Only touches 1 player’s data
####4. 最佳实践
-批量更新:对非冲突操作进行分组(例如,DAO 中的投票).
-事件驱动:尽可能发出事件而不是突变状态.
struct VoteEvent has copy, drop { voter: address, choice: u8 }
使用共享对象进行多作者访问(例如游戏、DAO). 关键模式:
自有与共享
拥有:单一作家(更快). 共享:并发写入(更高的 gas). 并发处理
Sui 自动对共享对象 TxS 进行序列(无需手动锁定). 将热门数据拆分为子对象(例如,每个玩家 1 个)以减少冲突. 缓解冲突
隔离高流量字段(例如,表<ID, PlayerData>). 使用 PTB 进行原子多对象操作. 注意:
冲突交易:拆分数据或批量更新. 共享物体上的气体峰值. (Sui 的运行时处理冲突——通过数据分片进行优化. )
In Sui Move, shared objects (marked with key + store) are essential for multi-user interaction but require careful concurrency design. Use them when state must be globally mutable (e.g., games, DAOs), while owned/immutable objects suit single-user scenarios. Sui serializes transactions per shared object automatically, but frequent writes may trigger ConflictTransaction errors—mitigate this by splitting hot fields into separate child objects (e.g., one per user) or batching updates. For high throughput, adopt event-sourcing patterns: store actions as immutable events and aggregate state periodically. Always minimize mutable shared fields and prefer atomic swaps via transfer::share_object over direct mutations
共享对象与自有对象:何时使用每个对象
在以下情况下使用共享对象: -多个用户需要改变同一个状态 -该对象代表公共资源(例如游戏竞技场或 DAO) -你需要未经许可的访问模式
在以下情况下使用自有物品: -只有一个地址可以控制该对象 -你想充分利用 Sui 的并行执行 -对象代表用户特定的资产
并发管理策略
1. 子对象模式(减少争用)
将经常更新的字段拆分为单独的对象:
struct GameArena has key {
id: UID,
// Immutable or rarely changed fields
config: ArenaConfig,
// Split mutable state
player_state: Table<address, PlayerState>,
leaderboard: Leaderboard
}
struct PlayerState has key, store {
id: UID,
health: u8,
score: u64
}
2. 使用矢量/表格进行批量更新
struct DAOVault has key {
id: UID,
// Store multiple pending actions
pending_votes: Table<ID, Vote>,
pending_deposits: Table<address, Deposit>
}
public entry fun batch_process(
vault: &mut DAOVault,
ctx: &mut TxContext
) {
// Process all pending items at once
let votes = table::remove_all(&mut vault.pending_votes);
process_votes(votes);
}
3. 基于纪元的处理
struct LendingPool has key {
id: UID,
current_epoch: u64,
next_epoch_data: EpochData,
current_epoch_data: EpochData
}
public entry fun advance_epoch(pool: &mut LendingPool) {
let finished = pool.current_epoch_data;
pool.current_epoch_data = pool.next_epoch_data;
pool.next_epoch_data = new_epoch_data();
process_finished_epoch(finished);
}
避免冲突的技巧
- 字段级分区:
struct HighTrafficObject has key {
id: UID,
// Split into separate objects per "hot" field
stats_by_category: Table<u8, CategoryStats>
}
- 延迟执行队列:
struct ActionQueue has key {
id: UID,
pending: VecMap<u64, Action>
}
public entry fun execute_ready_actions(
queue: &mut ActionQueue,
ready_up_to: u64
) {
// Processes actions in batches
}
3.带有版本控制的乐观并发性:
struct Versioned has key {
id: UID,
version: u64,
data: vector<u8>
}
public entry fun update(
obj: &mut Versioned,
new_data: vector<u8>,
expected_version: u64
) {
assert!(obj.version == expected_version, EBAD_VERSION);
obj.data = new_data;
obj.version = expected_version + 1;
}
实用 CLI 示例
在提交之前检查对象依赖关系:
sui client inspect-transaction --serialize-unsigned <TX_BYTES>
模拟争用场景:
sui move test --path <YOUR_PACKAGE> --gas-budget 10000
最佳实践摘要
- 最大限度地减少共享对象突变:设计用于不经常写入共享对象
- 使用子对象:分解状态以减少争用点 3.批量操作:将相关更改分组为单笔交易
- 考虑混合模型:将自有物体与偶尔共享协调相结合
sui-tool replay
监控热点:用于分析争用
请记住,Sui 的运行时会通过其对象模型自动处理某些并发性,但您仍然需要架构数据结构以最大限度地减少逻辑冲突.
当多个用户需要与同一个状态进行交互且这些用户不在同一个所有者之下时,你应该在 Sui Move 中使用共享对象. 示例包括向所有玩家开放的游戏竞技场、从不同地址收集选票的 DAO 投票追踪器,或者任何人都可以存入的借贷池. 共享对象可通过共识在全球范围内访问和更新,这与自有对象不同,后者绑定到单个地址并在未达成完全共识的情况下进行修改.
Sui 通过要求对任何改变共享对象的交易达成完全共识来处理共享对象的并发性. ConflictTransaction
当两个用户几乎同时尝试变更同一个共享对象时,只有一个事务成功——其他交易因错误而被拒绝. 这使得共享对象变得强大,但如果设计不周密,也会带来争用问题.
为了在不经常ConflictTransaction
出现错误的情况下支持高并发性,你应该使用隔离可变状态的设计模式. 与其将所有可变数据存储在一个共享对象中,不如将其拆分为多个子对象,每个子对象都有自己的所有权或共享状态. 例如,在游戏竞技场中:
struct Arena has key, store {
game_id: u64,
players: vector<address>,
}
struct PlayerState has key, store {
hp: u64,
energy: u64,
}
设为Arena
共享,但为每个人提供PlayerState
自己的共享对象. 这样,不同的玩家可以独立更新自己的状态而不会发生冲突.
另一种模式是**“索引+存储桶”**策略:保留指向单个存储桶或条目的共享索引对象. 仅访问索引以获取正确的条目,然后对条目本身进行操作. 这减少了用户交易之间的重叠量.
如果您需要严格的排序(例如,用于拍卖),请在共享对象内使用矢量或地图来实现手动排队,但要知道每个突变仍然需要共识,并且在加载时可能会失败. 在这种情况下,可以考虑将排序转移到前端或批量写入.
总而言之:
- 当状态必须由许多独立用户访问时,使用共享对象.
- 避免将所有可变字段放入单个共享对象——将它们拆分成子对象.
- 使用共享 + 动态子对象来降低冲突率.
- 预计
ConflictTransaction
竞争激烈时会出错,然后在前端重试. - 尽可能选择仅追加更新,而不是就地突变.
当你在 Sui Move 中使用共享对象进行设计时,你本质上是在便于多用户访问和争议风险之间做出选择. 由于 Sui 使用以对象为中心和面向资源的模型,因此正确的模式取决于您的用例是需要协作访问还是隔离控制. 以下是思考这个问题的结构化方式:
###何时使用共享对象与自有对象
*当单个用户需要完全控制资产(例如钱包的硬币、玩家的库存或独特的NFT)时,拥有的物品是最佳选择. 除非重复使用相同的对象,否则涉及它们的交易不会发生冲突. *当多个用户需要读取或写入同一个状态(例如,DAO 治理记录、游戏竞技场排行榜、借贷池金库)时,共享对象才有意义. 它们无需许可的传输即可实现全球访问,但它们也存在并发性挑战.
###Sui 中的并发管理
ConflictTransaction
Sui 的运行时会自动序列化触及同一个可变共享对象的交易,这意味着如果两个用户尝试更新同一个共享对象,一个会成功,另一个会出错. 您无需手动编码锁定或队列——冲突解决是在执行层处理的. 但是,频繁的冲突会降低吞吐量,因此您的任务是设计可最大限度地减少重叠的状态布局.
###避免频繁冲突的最佳做法
*拆分可变状态:不是单个 “超级对象”,而是将其分解为较小的子对象. 例如,在借贷金库中,将每个用户的持仓作为自己拥有或部分共享的对象分开,使金库本身基本保持静态. 这减少了写入争用. *尽可能使用不可变字段:将元数据或参考数据存储在不可变的子对象中,这样只读的事务就不会与写入的事务发生冲突. *小心批量操作:如果你的合约设计需要对同一个共享对象进行多次写入,可以考虑将它们批处理成一个交易区块以避免多次冲突. *分片模式:对于排行榜或 DAO 之类的东西,按组、回合或标识符对状态进行分片,这样并非所有交易都会到达完全相同的对象路径.
###需要注意的陷阱
*单片共享对象:将所有内容放在一个共享对象中会导致高度的争用和负载下的持续冲突. *过度使用共享对象:如果可以将数据建模为所有者,则更喜欢这种路径——它更便宜,也不太可能出现瓶颈. *忽略访问模式:如果大多数交互都是在很少写入的情况下读取的,则围绕不可变的快照进行设计,而不是围绕一个可变的共享状态进行设计.
###实际例子
想象一下构建 DAO 投票追踪器:
*错误模式:存储每一次投票的单个共享对象,由所有参与者更新.
*更好的模式:每个投票者创建一个Vote
引用提案的自有对象,而共享的 DAO 对象仅定期汇总结果. 这样,平行投票就不会发生冲突.
###交易区块
输入 → 用户针对共享或拥有的对象提交交易. 流程 → Sui 的运行时序列化同一个可变共享对象上的事务. 结果 → 成功提交写入,其他写入会出现冲突错误;拆分对象可降低发生冲突的机会.
Great questions—this is exactly the crux of building “high-fanout” apps on Sui. Here’s a practical playbook you can follow.
🧭 When to use a shared object (vs owned)
Use a shared object only when multiple independent parties must mutate the same piece of state and you need one canonical result (e.g., AMM pool reserves, an arena’s match registry, a DAO proposal’s tally). Prefer owned objects when the state is per-user, per-position, or per-item and can evolve independently (inventories, user balances, open positions).
Rule of thumb: put the coordination surface in a shared object, and everything else in owned objects that can run in parallel.
⚙️ What Sui does with concurrent access
- Any tx that mutates a shared object goes through consensus and is totally ordered. Conflicting writes to the same fields are serialized; others proceed in parallel.
- You don’t manually lock; you design to avoid hotspots. Conflicts show up as
ConflictTransaction
when many txs try to bump the same version.
🏗️ High-concurrency design patterns (with Move tips)
1) Root-manager + owned positions
Keep a minimal shared root; put user/position state in owned objects so deposits/claims mostly touch owned data.
module example::vault {
use sui::tx_context::TxContext;
struct Vault has key { /* config only; no growing vectors */ }
struct Position has key { owner: address, /* state */ }
/// Shared root only needed to mint/close positions or checkpoint.
public entry fun open_position(v: &mut Vault, ctx: &mut TxContext): Position { /* ... */ }
/// Hot path touches owned Position only → parallel.
public entry fun deposit(pos: &mut Position, amount: u64) { /* ... */ }
}
Why it scales: most traffic avoids the shared root altogether.
2) Shard by key (dynamic fields / tables)
Instead of one big shared map, create N shard objects (shared) under a registry and route by hash(key) % N
. Each shard is a separate contention domain.
/// registry is shared; it stores IDs of shard objects
struct Registry has key { shards: vector<ID> }
/// choose shard once and keep it stable for that key
fun shard_for(reg: &Registry, key: &vector<u8>): ID { /* hash(key) % len */ }
Tip: don’t update the registry on every user op; mutate only the target shard.
3) Dynamic fields on a shared “anchor”, not growing vectors
Avoid vector.push
on a shared object (resizes = conflicts). Use dynamic fields (key → value) so each entry mutates independently.
SharedAnchor
└─ df[key=user] = tiny record (or pointer to owned object)
Move APIs: sui::dynamic_field::{add, borrow_mut, remove}
.
4) Two-phase “ticket” pattern
Have the shared object mint a ticket/capability (owned) that authorizes a later operation done off the hot path.
- Light touch on shared root → mint
Ticket
. - User completes heavy work by presenting the
Ticket
and mutating only owned/child objects.
Reduces churn on the shared root, and gives you idempotency (ticket can be one-time-use).
5) Append-only events, deferred aggregation
If you’re tempted to keep a total_*
counter on the shared root (a hotspot), emit events on each action and aggregate off-chain or in a periodic checkpoint entry that rolls up per-shard totals.
6) Split mutable fields into sub-objects
If one field changes much more frequently (e.g., queue head), peel it into its own child object (owned or shared as needed). Then updates don’t bump the root’s version.
🧪 Avoiding ConflictTransaction
in practice
- Keep the cut set small: Each entry function should touch as few objects as possible.
- No growing vectors on shared roots; use dynamic fields or sharded children.
- Stable routing: Deterministic key→shard mapping; never rehash live keys.
- Idempotency: Use tickets/nonces so retries don’t double-apply.
- Batch carefully: If you must touch multiple entries atomically, use a PTB—but keep it within a single shard when possible.
- Backoff & retry: On the client, exponential backoff for known conflict aborts.
🔍 Minimal conflict checklist
- Shared root is configuration + pointers only.
- Hot paths mutate owned positions or sharded children.
- Per-user/per-item state is not inside the root.
- No
vector.push
on shared; use dynamic fields. - Periodic checkpoint function consolidates, not every tx.
🛡️ Security & correctness notes
- Gate privileged ops with capabilities (e.g.,
AdminCap
,MintCap
); store caps in owned objects, not globals. - Validate invariants in shared entry points (sum of shard totals, proposal status transitions, etc.).
- Emit events for auditability; they are cheap and conflict-free.
Quick templates by use case
- Game arena: Shared
Arena
(config/match index) + ownedPlayer
/Loadout
. Match joins go toShard[hash(match_id)]
. - DAO voting: Shared
Proposal
(immutable metadata) + dynamic fields per voter or ownedBallot
tickets. Tally via periodic checkpoint. - Lending vault: Shared
Registry
+ ownedPosition
s; price/oracle reads are immutable; rebalances via sharded pools.
你知道答案吗?
请登录并分享。
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
