查看原文
其他

浅谈IC Storage存储技术

ICPLabs ICPLabs 2022-03-21

“经过社区的讨论,Dfinity 开放了提升 Canister 内存容量的 NNS 提案,目前,该提案已经由社区投票通过。该提案建议为容器设计一个新的系统 API,使得 Canister 可使用的内存增加,后续会有代码的正式更新。”

Canister

Canister的总体内存包含wasm run time和stable memory,对一个Canister而言, 两个都为4G。

由于wasmtime是32bit的指针地址空间,因此是4G,而stable memory需要和wasm time适配, 所以也是4G,本次提案提高的内存为Stable内存, 使一个Canister可使用的最大内存为全部子网内存, 但此次开放的API将只支持Canister最大使用8G Stable内存。


Wasm 单体的内存空间为4G, 但是由于Motoko所选用的GC(Garbage Collector 垃圾回收)算法为Copying算法[1],造成由Motoko写的Canister只能使用2G内存空间。

[1]Coping Collector Algorithm(Minor GC),该算法在WASM Canister中的应用:

1. 4G 的Canister被划分为两个区域 :from space & to space, 分别占用2G;数据写入时, 所有的数据都在from space中。

2. 在进行dfx canister install xxx(default : --all) -- mode upgrade的时候, 会进行GC,即:将活动对象(正在使用的对象,可以是引用也可以是元数据)从from space移动到to space, 剩下的即内存垃圾, 接下来,删除掉from space中的数据(内存垃圾), 最后将from space 与 to space的名称互换。
3. 优势:内存使用率很高,写数据时是对堆进行连续写入, 所以利用率更高 。适用于快速增删数据的场景。(现在Java的JVM等新一代VM都一定程度上采用了Minor GC)。
4. 缺陷:浪费了一半的堆内存空间, 并且在活体对象数量大时不建议使用Coping gc。
P.S. Motoko现在已可指定Compacting GC算法, 这个算法不会占用一半的内存来进行GC,可以使用尽可能大的WASM内存。
5. 对4G stable内存进行读写仅发生在upgrade过程。

这带来的劣势是:当堆内存在升级时有大量要写入stable内存的数据时,可能会将cycle消耗完,导致无法升级。
6. 触发时间:coping gc触发时间是在新生成的变量所 需内存大于已有的变量(2G-已用内存)或者当前已用内存大于2G时。
存储提案

 https://forum.dfinity.org/t/increased-canister-storage/6148/67

提案内容:提供一个系统API, 使每个Canister都可以使用子网所有的内存空间, 所有的副本都有自己的数据状态, 每个Canister均有自己的WASM运行时内存和Stable内存空间。

新增的System API (面向Canister):

ic0.stable64_write: (offset: i64, src: i64, size: i64) -> ()

ic0.stable64_write接口是为了WASM 64进行准备的

ic0.stable64_read: (dst: i64, offset: i64, size: i64) -> ()
ic0.stable64_size: () -> (page_count: i64)
ic0.stable64_grow: (additional_pages: i64) -> (old_page_count: i64
提案现状:
    • 提案于9.3日通过NNS审核, 接下来就是更新代码的事情。提案只是一个征求社区看法的提案。
     •  最近将提交修改IC代码的提案。

带来影响:

    • 对Canister而言, Canister可以使用的内存从4G -> 8G(stable内存而非堆内存)。并且 8G是当前提案的结果, 以后会根据社区反馈会提升这个数字。

    • 如果stable内存高于4G, 那么使用了升级内存API的Canister和没有使用此API的Canister交互将会出现问题,当前的方案是发生上述情况时,Canister会崩溃。

未来解决方案:
    • 沿用现在的解决方案 :子网内存共享, 3T内存分出300G给stable内存存储(尚不清楚300G这个数字是怎么来的, 8G是怎么来的)。这样的缺陷是:这种情况下,存储内存最大限制就是一个子网内的stable内存的限制, 这显然是不能长久的。
    • BigMap:通过多个Canister分片存储数据,当前状况是没有达到生产级别的要求, 还没有放出。

    • WASM 64 :单个Canister升级为64bit的地址空间, 最大内存为2^64 Byte(16 T)• 数据迁移所牵扯的数据量会很大,会出现新的问题,比如升级时数据迁移量大等。

建议:
    • 现阶段还是一个实验性的API, 建议不要使用。等待成熟以后再使用。
如何获取Canister存储状态

Motoko

RTS : Run Time System 运行时系统, 包含GC, 序列化(通信传输用), low-level 库(底层分配内存等)的调用。
rts_memory_size: () -> Nat :当前WASM的内存, 不是使用了多少的内存, 而是已经分配了的堆内存;
rts_heap_size: () -> Nat :当前实际堆内存大小;
rts_max_live_size : () -> Nat :从上次GC到现在堆最大的大小;

Rust

• 使用IC "aaaaa-aa" Actor可以访问IC.status, 也可以返回上面说到的内存数据;
• Rust可以在非Upgrade时期, 通过cdk中提供的API操作Stable内存。

DFX & Moc

moc 中flag可选 --compacting gc 来更换coping gc, compacting gc下motoko编译的Canister将可以使用4G 的WASM 内存。

Stable内存

stable只能用于需要持久化存储的全局变量,只在upgrade的时候写入stable内存用。对于Canister, Canister的每个Replica都有自己的stable内存, 并非所有的Replica共享一份Stable内存。

关于数据存储的其他Tips

1. 作用于存储的数组最好使用Blob而非[Nat8], 因为初始化数组时, 由于泛型的需要, 所有通过[X]或者Array.init初始化的数组内存布局是相同的。
2. 当前比较成熟的解决方案:存储的数据直接放入stable内存中, 堆内存用来放置检索数据。

外部链接

[1] Forum Stable Memory Roadmap https://forum.dfinity.org/t/increased-canister-storage/6148/70?u=c-b-elite

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存