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 几年前就经历过这种情况,他们不得不付出巨大的工程努力来摆脱这种模式。