#internet-computer #dfinity #stable-structures

b3-stable-structures

用于无恐惧的canister升级的数据结构集合

3个版本

0.5.9 2023年9月7日
0.5.8 2023年9月7日
0.5.7 2023年8月8日

#415 in 数据结构

每月 22 次下载

Apache-2.0

280KB
5.5K SLoC

Crate Info Apache-2.0 API Docs Chat on the Forum

稳定结构

一组用于互联网计算机的可扩展数据结构,这些结构在升级过程中持久存在。

稳定结构旨在使用稳定内存作为后端存储,允许它们增长到千兆字节大小,而无需pre_upgrade/post_upgrade钩子。

背景

传统的canister状态持久化方法是在pre_upgrade钩子中将整个状态序列化到稳定内存中,并在post_upgrade钩子中解码它。这种方法易于实现,并且对于相对较小的数据集效果良好。不幸的是,它扩展性不好,可能会使canister无法升级。

这个库旨在简化直接在稳定内存中管理数据结构。有关库背后的哲学更多信息,请参阅Roman关于稳定结构的教程

可用的数据结构

  • [BTreeMap]: 键值存储
  • [Vec]: 可增长的数组
  • [Log]: 变量大小条目的只追加列表
  • [Cell]: 可序列化的值
  • [MinHeap]: 优先队列。

工作原理

稳定结构能够直接在稳定内存中工作,因为每个数据结构都管理自己的内存。当初始化稳定结构时,提供一段内存供数据结构使用以存储其数据。

以下是一个基本示例

use b3_stable_structures::{ BTreeMap, DefaultMemoryImpl };
let mut map: BTreeMap<u64, u64, _> = BTreeMap::init(
  DefaultMemoryImpl::default()
);

map.insert(1, 2);
assert_eq!(map.get(&1), Some(2));

使用[Memory]特质将内存抽象化,稳定结构可以与任何实现此特质的存储后端一起工作。这包括稳定内存、向量([VectorMemory]),甚至是平面文件([FileMemory])。

上面的示例使用[DefaultMemoryImpl]初始化了一个[BTreeMap],当在canister中使用时映射到稳定内存,在其他情况下映射到[VectorMemory]。

请注意,稳定结构不能共享内存。每个内存只能属于一个稳定结构。例如,当在canister中运行时,这将失败

use b3_stable_structures::{BTreeMap, DefaultMemoryImpl};
let mut map_1: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());
let mut map_2: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());

map_1.insert(1, 2);
map_2.insert(1, 3);
assert_eq!(map_1.get(&1), Some(2)); // This assertion fails.

它失败是因为底层 map_1map_2 都使用了相同的稳定内存,因此对 map_1 的更改最终会导致 map_2 发生更改或损坏。

为了解决这个问题,我们使用了 MemoryManager,它接受一个内存并为我们创建最多 255 个虚拟内存。以下是上述失败示例,但通过使用 MemoryManager 得到了修复

use b3_stable_structures::{
  memory_manager::{ MemoryId, MemoryManager },
  BTreeMap,
  DefaultMemoryImpl,
};
let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default());
let mut map_1: BTreeMap<u64, u64, _> = BTreeMap::init(
  mem_mgr.get(MemoryId::new(0))
);
let mut map_2: BTreeMap<u64, u64, _> = BTreeMap::init(
  mem_mgr.get(MemoryId::new(1))
);

map_1.insert(1, 2);
map_2.insert(1, 3);
assert_eq!(map_1.get(&1), Some(2)); // Succeeds, as expected.

示例 Canister

这是一个完全工作的 canister 示例,将所有内容结合起来。

依赖项

[dependencies]
ic-cdk = "0.6.8"
ic-cdk-macros = "0.6.8"
b3-stable-structures = "0.5.6"

代码

use b3_stable_structures::memory_manager::{
  MemoryId,
  MemoryManager,
  VirtualMemory,
};
use b3_stable_structures::{ DefaultMemoryImpl, StableBTreeMap };
use std::cell::RefCell;

type Memory = VirtualMemory<DefaultMemoryImpl>;

thread_local! {
  // The memory manager is used for simulating multiple memories. Given a `MemoryId` it can
  // return a memory that can be used by stable structures.
  static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> =
    RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));

  // Initialize a `StableBTreeMap` with `MemoryId(0)`.
  static MAP: RefCell<StableBTreeMap<u128, u128, Memory>> = RefCell::new(
    StableBTreeMap::init(
      MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(0)))
    )
  );
}

// Retrieves the value associated with the given key if it exists.
#[ic_cdk::query]
fn get(key: u128) -> Option<u128> {
  MAP.with(|p| p.borrow().get(&key))
}

// Inserts an entry into the map and returns the previous value of the key if it exists.
#[ic_cdk::update]
fn insert(key: u128, value: u128) -> Option<u128> {
  MAP.with(|p| p.borrow_mut().insert(key, value))
}

更多示例

组合持久性

如果您的项目仅依赖于稳定结构,则内存可以在不要求 pre_upgrade/post_upgrade 钩子的情况下扩展其大小。

然而,需要注意的是,如果您还打算对堆数据进行序列化/反序列化,使用内存管理器变得必要。为了有效地结合这两种方法,请参考 快速入门示例 以获取指导。

依赖项