CouchDB 上的哪个数据结构具有三个实体(用户、文件夹、文件)?
Which data-structure on CouchDB with three entities (User, Folder, Files)?
我正在尝试在 CouchDB 中为类似 Dropbox 的场景构建一个 "relationship":
- 用户
- 文件夹
- 文件
到目前为止,我正在纠结是引用还是嵌入上述内容,还没有解决权限问题。在我的场景中,我只想存储文件的路径,不想使用附件。这是我拥有的:
选项 1(单独的文档)
在这里,我将所有内容链接在一起,它(至少对我而言)似乎是 RDBMS 模型的副本,这不应该是使用 NoSQL 时的目标。
{
"id": "user1",
"type": "user",
"folders": [
"folder1",
"folder2"
]
}
{
"id": "folder1",
"type": "folder",
"path": "\user1\pictures",
"files": [
"file1",
"file2"
]
}
{
"id": "file1",
"type": "file",
"name": "myDoc.txt",
}
选项 2(单独的文档)
在此选项中,我将保留用户文档,并将用户 ID 放入文件夹文档中以供参考。
{
"id": "user1",
"type": "user",
}
{
"id": "folder1",
"type": "folder",
"path": "\user1\pictures",
"owner" "user1",
"files": [
"file1",
"file2"
]
}
{
"id": "file1",
"type": "file",
"name": "myDoc.txt",
}
选项 3(嵌入文档)
与选项 2 类似,我在这里将忽略第三种文档类型 files,并将所有内容嵌入到文件夹文档中。我读到这只是一个选项,如果我不需要存储很多项目并且我不知道用户将存储多少项目。
{
"id": "user1",
"type": "user",
}
{
"id": "folder1",
"type": "folder",
"path": "\user1\pictures",
"owner" "user1",
"files": [{
"id": "file1",
"type": "file",
"name": "myDoc1.txt"
}, {
"id": "file2",
"type": "file",
"name": "myDoc2.txt"
}
]
}
选项 4
我也可以将所有内容都放在一个文档中,但在这种情况下这是没有意义的。 JSON 文档会及时变大,这在性能/加载时间方面不是我们想要的。
结论
对我来说 none 上述选项似乎适合我的情况,如果您能就如何在 CouchDB 中设计适当的数据库模式提供一些意见,我将不胜感激。或者,也许上述选项之一已经是一个好的开始,但我只是没有看到它。
数据建模从应用程序将使用的查询开始。
如果您的查询是用户看到所有 his/her 个文件夹,并且打开一个文件夹会显示它下面的所有文档和子文件夹,那么选项 1 自然适合查询。
但是,您首先需要回答一个非常重要的问题,尤其是对于 CouchDB。你的数据库有多大。如果您需要跨多个节点分区的数据库,那么性能会受到影响,可能会达到数据库变得无响应的程度。因为打开一个包含许多文档的文件夹将意味着搜索每个分区。这是因为分区是由用户无法控制的 ID 的哈希值决定的。对于小型单节点(或非分区)数据库,性能会很好。
选项 2 要求您在 "owner" 上构建索引,这与选项 1 的原因相同。
选项 3/4 是一种反规范化,它解决了上述性能问题。如果文档很大并且经常更新,则存储开销和压缩成本可能会很大。您需要针对特定工作负载进行基准测试。
总而言之,如果您的目标数据库很大且已分区,则没有简单的答案。需要仔细的原型和基准测试。
为了给你一个具体的想法,我会像这样模拟一个 Dropbox 克隆:
- 共享:共享的根文件夹。无需为子文件夹建模,因为它们没有不同的权限。在这里我可以设置文件夹的物理位置和允许使用它们的用户。我希望每个用户只有几个共享,因此您可以将共享列表保存在内存中。
- 文件:共享中的实际文件。根据您的用例,无需将文件保存在数据库中,因为文件系统本身已经是一个很棒的文件数据库!如果您需要散列和删除重复文件(例如 Dropbox 做的),那么您可以在 CouchDB 中创建一个缓存。
这将是文档结构:
{
"_id": "share.pictures",
"type": "share",
"owner": "Alice",
"writers": ["Bob", "Carl"],
"readers": ["Dorie", "Eve", "Fred"],
"rootPath": "\user1\pictures"
},
{
"_id": "file.2z32236e2sdwhatever",
"type": "file",
"path": ["vacations", "2017 maui"],
"filename": "DSC1234.jpg",
"size": 12356789,
"hash": "1235a",
"createdAt": "2017-07-29T15:03:20.000Z",
"share": "share.pictures"
},
{
"_id": "file.sdfwhatever",
"type": "file",
"path": ["vacations", "2015 alaska"],
"filename": "DSC12345.jpg",
"size": 11,
"hash": "acd5a",
"createdAt": "2017-07-29T15:03:20.000Z",
"share": "share.pictures"
}
这样您就可以按共享和路径构建文件的 CouchDB 视图,并按文件夹进行查询:
function (doc) {
if (doc.type === 'file') emit([doc.share].concat(doc.path), doc.size);
}
如果需要,您还可以添加仅需 _sum
的 reduce 函数,并免费获得分层大小计算器(好吧,几乎)!
假设您调用了数据库 'dropclone' 并将视图添加到名为 'dropclone' 且视图名称为 'files' 的设计文档中,您将像这样查询它:
http://localhost:5984/dropclone/_design/dropclone/_view/files?key=["share.pictures","vacations"]
你会得到 123456800
结果。
对于
http://localhost:5984/dropclone/_design/dropclone/_view/files?key=["share.pictures","vacations"]&reduce=false&include_docs=true
您会得到这两个文件。
您也可以将整个共享名称和路径添加到_id 中,因为这样您就可以通过已知路径直接访问每个文件。您仍然可以冗余地添加路径或将其保留并动态地将 _id 拆分为其路径组件。
其他方法是:
- 每个共享使用一个 CouchDB 数据库并使用 CouchDB 的 _security 机制来管理访问。
- 将文件拆分成块,对它们进行哈希处理并存储每个文件的块哈希值。这样您就可以对整个文件系统进行虚拟化和重复数据删除。这就是 Dropbox 在幕后为节省存储所做的工作 space。
你不应该做的一件事是将文件本身存储到 CouchDB 中,这会很快变脏。 NPM 几年前就经历过这种情况,他们不得不付出巨大的工程努力来摆脱这种模式。
我正在尝试在 CouchDB 中为类似 Dropbox 的场景构建一个 "relationship":
- 用户
- 文件夹
- 文件
到目前为止,我正在纠结是引用还是嵌入上述内容,还没有解决权限问题。在我的场景中,我只想存储文件的路径,不想使用附件。这是我拥有的:
选项 1(单独的文档)
在这里,我将所有内容链接在一起,它(至少对我而言)似乎是 RDBMS 模型的副本,这不应该是使用 NoSQL 时的目标。
{
"id": "user1",
"type": "user",
"folders": [
"folder1",
"folder2"
]
}
{
"id": "folder1",
"type": "folder",
"path": "\user1\pictures",
"files": [
"file1",
"file2"
]
}
{
"id": "file1",
"type": "file",
"name": "myDoc.txt",
}
选项 2(单独的文档)
在此选项中,我将保留用户文档,并将用户 ID 放入文件夹文档中以供参考。
{
"id": "user1",
"type": "user",
}
{
"id": "folder1",
"type": "folder",
"path": "\user1\pictures",
"owner" "user1",
"files": [
"file1",
"file2"
]
}
{
"id": "file1",
"type": "file",
"name": "myDoc.txt",
}
选项 3(嵌入文档)
与选项 2 类似,我在这里将忽略第三种文档类型 files,并将所有内容嵌入到文件夹文档中。我读到这只是一个选项,如果我不需要存储很多项目并且我不知道用户将存储多少项目。
{
"id": "user1",
"type": "user",
}
{
"id": "folder1",
"type": "folder",
"path": "\user1\pictures",
"owner" "user1",
"files": [{
"id": "file1",
"type": "file",
"name": "myDoc1.txt"
}, {
"id": "file2",
"type": "file",
"name": "myDoc2.txt"
}
]
}
选项 4
我也可以将所有内容都放在一个文档中,但在这种情况下这是没有意义的。 JSON 文档会及时变大,这在性能/加载时间方面不是我们想要的。
结论
对我来说 none 上述选项似乎适合我的情况,如果您能就如何在 CouchDB 中设计适当的数据库模式提供一些意见,我将不胜感激。或者,也许上述选项之一已经是一个好的开始,但我只是没有看到它。
数据建模从应用程序将使用的查询开始。 如果您的查询是用户看到所有 his/her 个文件夹,并且打开一个文件夹会显示它下面的所有文档和子文件夹,那么选项 1 自然适合查询。
但是,您首先需要回答一个非常重要的问题,尤其是对于 CouchDB。你的数据库有多大。如果您需要跨多个节点分区的数据库,那么性能会受到影响,可能会达到数据库变得无响应的程度。因为打开一个包含许多文档的文件夹将意味着搜索每个分区。这是因为分区是由用户无法控制的 ID 的哈希值决定的。对于小型单节点(或非分区)数据库,性能会很好。
选项 2 要求您在 "owner" 上构建索引,这与选项 1 的原因相同。
选项 3/4 是一种反规范化,它解决了上述性能问题。如果文档很大并且经常更新,则存储开销和压缩成本可能会很大。您需要针对特定工作负载进行基准测试。
总而言之,如果您的目标数据库很大且已分区,则没有简单的答案。需要仔细的原型和基准测试。
为了给你一个具体的想法,我会像这样模拟一个 Dropbox 克隆:
- 共享:共享的根文件夹。无需为子文件夹建模,因为它们没有不同的权限。在这里我可以设置文件夹的物理位置和允许使用它们的用户。我希望每个用户只有几个共享,因此您可以将共享列表保存在内存中。
- 文件:共享中的实际文件。根据您的用例,无需将文件保存在数据库中,因为文件系统本身已经是一个很棒的文件数据库!如果您需要散列和删除重复文件(例如 Dropbox 做的),那么您可以在 CouchDB 中创建一个缓存。
这将是文档结构:
{
"_id": "share.pictures",
"type": "share",
"owner": "Alice",
"writers": ["Bob", "Carl"],
"readers": ["Dorie", "Eve", "Fred"],
"rootPath": "\user1\pictures"
},
{
"_id": "file.2z32236e2sdwhatever",
"type": "file",
"path": ["vacations", "2017 maui"],
"filename": "DSC1234.jpg",
"size": 12356789,
"hash": "1235a",
"createdAt": "2017-07-29T15:03:20.000Z",
"share": "share.pictures"
},
{
"_id": "file.sdfwhatever",
"type": "file",
"path": ["vacations", "2015 alaska"],
"filename": "DSC12345.jpg",
"size": 11,
"hash": "acd5a",
"createdAt": "2017-07-29T15:03:20.000Z",
"share": "share.pictures"
}
这样您就可以按共享和路径构建文件的 CouchDB 视图,并按文件夹进行查询:
function (doc) {
if (doc.type === 'file') emit([doc.share].concat(doc.path), doc.size);
}
如果需要,您还可以添加仅需 _sum
的 reduce 函数,并免费获得分层大小计算器(好吧,几乎)!
假设您调用了数据库 'dropclone' 并将视图添加到名为 'dropclone' 且视图名称为 'files' 的设计文档中,您将像这样查询它:
http://localhost:5984/dropclone/_design/dropclone/_view/files?key=["share.pictures","vacations"]
你会得到 123456800
结果。
对于 http://localhost:5984/dropclone/_design/dropclone/_view/files?key=["share.pictures","vacations"]&reduce=false&include_docs=true
您会得到这两个文件。
您也可以将整个共享名称和路径添加到_id 中,因为这样您就可以通过已知路径直接访问每个文件。您仍然可以冗余地添加路径或将其保留并动态地将 _id 拆分为其路径组件。
其他方法是:
- 每个共享使用一个 CouchDB 数据库并使用 CouchDB 的 _security 机制来管理访问。
- 将文件拆分成块,对它们进行哈希处理并存储每个文件的块哈希值。这样您就可以对整个文件系统进行虚拟化和重复数据删除。这就是 Dropbox 在幕后为节省存储所做的工作 space。
你不应该做的一件事是将文件本身存储到 CouchDB 中,这会很快变脏。 NPM 几年前就经历过这种情况,他们不得不付出巨大的工程努力来摆脱这种模式。