容器 ID
容器 ID(Container ID)是容器的唯一标识,分为两种形式:
- 根容器:由类型与根名称组成;
- 普通容器:通过用户操作创建,由 ID 与类型组成。
Rust ContainerID 定义
pub enum ContainerID {
Root {
name: InternalString,
container_type: ContainerType,
},
Normal {
peer: PeerID,
counter: Counter,
container_type: ContainerType,
},
}TypeScript ContainerID 类型
export type =
| `cid:root-${string}:${}`
| `cid:${number}@${}:${}`;-
根容器(Root Containers)
- 首次访问根容器时隐式创建,例如调用
doc.getText("text"),历史中不会生成操作记录; - 由字符串名称与容器类型唯一确定。
- 首次访问根容器时隐式创建,例如调用
-
普通容器(Normal Containers)
- 通过
insertContainer、createNode等操作显式创建; - 在执行创建子容器的操作时自动生成;
- 容器 ID 中包含其创建操作的 Operation ID。
- 通过
容器状态与 ID
容器 ID 并非随机 UUID,而是基于容器上下文确定性生成。理解容器状态有助于掌握容器 ID 的生成方式。
关于容器的完整介绍(包括 attached / detached 状态),请参阅容器概念。
容器 ID 生成的要点:
- 根容器:ID 来源于名称(例如
doc.getText("text")中的 “text”); - 普通容器:ID 来源于创建它的操作(OpID);
- Detached 容器:在插入文档前使用默认的占位 ID。
容器覆盖
并行初始化子容器时,Loro 不会自动合并,而是可能发生覆盖。例如:
const : string = "hello";
const = new ();
const = .("map");
// 并行初始化子容器
const = .();
const = .("map").("text", new ());
.(0, "A");
const = .("map").("text", new ());
.(0, "B");
.(.({ : "update" }));
// 结果可能是 { "meta": { "text": "A" } } 或 { "meta": { "text": "B" } }如果不保留编辑历史,这种行为存在较大的数据丢失风险。即便完整历史仍在、理论上可以恢复数据,恢复过程也会相当复杂。
当容器承载大量数据或是文档的主要存储时,被覆盖可能导致关键信息被隐藏或丢失。因此需要谨慎、系统地初始化容器,以避免此类问题。
最佳实践
-
如果容器可能被并行初始化,优先在根层级进行初始化,而不是作为嵌套容器创建。
-
使用 Map 容器时:
- 尽可能在 Map 初始化阶段一次性创建所有子容器;
- 避免在 Map 中并发创建同名子容器,以免发生覆盖。
覆盖的根源在于并发创建子容器会生成不同的容器 ID,从而无法自动合并其内容。