本篇文档介绍如何恰当构建 Wilddog Sync 数据存储结构,以降低数据查询难度,提高查询速度。
理解数据结构
Wilddog Sync 数据结构类似 NoSQL 类型,数据以 JSON 为格式进行存储。没有传统关系型数据库中的表和记录等概念。
{ "users": { "Mchen": { "friends": { "Jack": true }, "name": "Mary Chen", "widgets": { "one": true, "three": true } }, "Jack": { ... }, "Harry": { ... } } }
|
使数据扁平化
Wilddog Sync 的工作方式是当你查询某个节点时,将会返回这个节点下的所有子节点。所以,如果采取过多嵌套的数据结构,在查询时会返回很多冗余数据。
例如,你只想查询所有 room 的 name、type 信息,但下面的结构会返回不需要的 messages 列表。
{ // 一个非常差的充满嵌套的数据结构。请勿模仿。 // 对"rooms"进行遍历查找来获得名字需要下载很多很多的 messages。 "rooms": { "one": { "name": "room alpha", "type": "private", "messages": { "m1": { "sender": "mchen", "message": "foo" }, "m2": { ... }, // 非常长的 messages 列表 } }, "two":{...} } }
|
正确的做法如下,应该尽量使数据扁平化,让数据分布到不同的路径下,提高查询效率。
{ // rooms数据节点下仅包含房间的基本信息和唯一ID。 "rooms": { "one": { "name": "room alpha", "type": "private" }, "two": { ... }, "three": { ... } },
//room成员可以很方便的的存取 "members": { "one": { "Mchen": true, "Harry": true }, "two": { ... }, "three": { ... }
},
//消息数据与其他数据分离开,这样我们在查询其他数据时就不收消息数据的影响,从而提升性能。 //消息数据可以通过room ID方便的分页和查询。 "messages": { "one": { "m1": { "sender": "Mchen", "message": "foo" }, "m2": { ... }, "m3": { ... } }, "two": { ... }, "three": { ... } }
}
|
使数据可扩展
在许多场景下,我们有双向查询数据的需求。此时需要在数据结构中添加必要的冗余,以提高查询的效率。
例如,你需要设计一个聊天室的数据结构,该结构包含两个对象: user 和 room。两者是双向关系,user 可以属于多个room,room 可以包含多个user 。
{ "users": { "Chen": { "name": "Mary Chen" }, "Rinchen": { "name": "Byambyn Rinchen" }, "Madi": { "name": "Hamadi Madi" } }, "rooms": { "room1": { "name": "Alpha Tango", "members": { "user1": "mchen", "user2": "brinchen", "user3": "hamadi" } }, "room2": { ... }, "room3": { ... } } }
|
如上设计,当 user 需要查询自己属于哪个 room 时,该数据结构会遍历所有的 room,效率极低。更严重的是,如果 user 没有权限查看所有的 room,就不能实现需求。
正确的做法如下,在 user 下存入所属 room 的信息
{ "users": { "Chen": { "name": "Mary Chen", // 在Mary的数据下,建立他所属 room 的索引。 "rooms": { "room1": true, "room2": true } }, ... }, "rooms": { ... } }
|
只需要读取/users/Chen/rooms/$room_id
,看它是否为 null 就可以了。
但这样做需要注意,如果 user 和 room 的关系发生变化,就需要更新两个地方的关系数据。