【问题标题】:Java - Google App Engine - modelling graph structures in Google DatastoreJava - Google App Engine - 在 Google Datastore 中建模图形结构
【发布时间】:2014-05-29 21:04:09
【问题描述】:

Google Apps Engine 提供 Google Datastore 作为唯一的 NoSQL 数据库(我认为它基于 BigTable)。

在我的应用程序中,我有一个类似社交的数据结构,我想像在图形数据库中那样对其进行建模。我的应用程序必须保存异构对象(用户、文件、...)以及它们之间的关系(例如 user1 OWNS file2、user2 FOLLOWS user3 等)。

我正在寻找一种模拟这种典型情况的好方法,我想到了两种解决方案:

  1. 基于列表的解决方案:任何对象都包含其他相关对象的列表,并且列表中存在的对象本身就是关系(正如 Google 在 JDO 部分 https://developers.google.com/appengine/docs/java/datastore/jdo/relationships 中所说)。

    李>
  2. 基于图形的解决方案:节点和关系都是对象。对象独立于关系而存在,而每个关系都包含对两个(或更多)连接对象的引用。

这两种方法的优点和缺点是什么?

关于方法一:这是大家能想到的比较简单的方法,官方文档中也有介绍,但是:

  • 每个有向关系都会使对象记录增长:对可能的关系数量是否有任何限制,例如对象维度限制?
  • 这是 JDO 功能还是数据存储结构允许自然实现该方法?
  • 关系搜索时间会随着列表的增加而增加,此解决方案是否适用于大型(百万)关系?

关于方法 2:每个关系都可以具有更高级别的表征(它是一个对象并且可以具有属性)。而且我认为内存大小不是谷歌的问题,而是:

  • 每个关系都需要自己的记录,因此每对相关夫妻的搜索时间会随着关系总数的增加而增加。这是否适合大量关系(数百万,数十亿)? IE。如果记录结构良好,谷歌是否有很好的技巧来搜索记录?或者我很快就会遇到这样的情况,如果我想搜索 User1 的一个名为 User4 的朋友,我必须等待几秒钟?
  • 另一方面,每个对象的维度都不会随着新关系的添加而增加。

您能否帮助我找到这两种方法的其他要点,以便选择最佳模型?

【问题讨论】:

    标签: java google-app-engine google-cloud-datastore nosql


    【解决方案1】:

    首先,数据存储区中的搜索时间不取决于您存储的实体数量,仅取决于您检索的实体数量。因此,如果您需要从十亿个关系对象中找到一个,所花费的时间就像您只有一个对象一样。

    其次,列表方法有一个严重的限制,称为“爆炸索引”。您必须索引包含列表的属性以使其可搜索。如果您使用的查询不仅仅引用此属性,您将遇到这个问题 - 谷歌它以了解其含义。

    第三,列表方法的成本要高得多。每次添加新关系时,都会以相当大的写入成本重写整个实体。如果您不能使用仅键查询,则阅读成本也会更高。使用对象方法,您可以使用仅键查询来查找关系,并且此类查询现在是免费的。

    更新:

    如果您的关系是定向的,您可以考虑使关系实体成为用户实体的子实体,并使用对象 ID 作为关系实体的 ID。那么您的关系实体将完全没有任何属性,这可能是最具成本效益的解决方案。您将能够使用纯键 ancestor queries 检索用户拥有的所有对象。

    【讨论】:

    • 除非必须,否则不要使用 JDO。请改用 Objectify 或低级 API。
    • 非常感谢您的回复!让我理解一下:如果我有十亿个类型为“A”的对象,其中包含两个文件 A.first 和 A.second,并且我想找到类型 A 的 X 条记录,这样 A.first=Y 那么查询时间不会t 取决于存储在数据库中的对象 A 的数量?所以我的性能与 1000 条记录相同?所以你建议第二种方法对吗?这种方法是否有一些关键方面(如爆炸索引)?
    • 或者您建议其他方法?
    • 你理解正确。最佳解决方案取决于每个对象的预期关系数量。如果您希望每个用户都有许多关系,那么将关系存储为实体是一种更好的方法。尝试尽可能多地使用仅键查询 - 这可能是数据模型中性能和成本的关键(双关语)。
    • 好吧,对不起,我是 GAE 的新手,我还没有完全理解重点。如果我正确理解,那么键是唯一标识对象并取决于其某些属性(例如名称空间和标识符)的东西。但是让 T 是一个表示关键字的实体,如果用户询问与 T 相关的所有前 X 个对象,我如何获得 T 键以便进行键查询?
    【解决方案2】:

    我有一个 AppEngine 应用程序,我同时使用这两种方法。哪个更好取决于两件事:可以存在多少关系的实际限制以及关系更改的频率。

    注意 1:我的回答是基于使用 Objectify 和大量使用缓存的经验。里程可能因其他方式而异。

    注意 2:我在这里使用了术语“id”而不是正确的 DataStore 术语“名称”。名称会令人困惑,而 id 匹配可以更好地客观化术语。

    考虑与他们就读过的学校相关联的用户,反之亦然。在这种情况下,你会同时做这两个。使用“列表”方法的变体将用户链接到学校。将用户参加的学校 ID 列表存储为具有不同类型/种类但与用户具有相同 ID 的 UserSchoolLinks 实体。例如,如果用户的 id = '6h30n' 存储一个 ID 为 '6h30n' 的 UserSchoolLinks 对象。每当您需要获取用户的学校列表时,都可以通过键查找加载这个单一实体。

    但是,不要对上过学校的用户执行相反的操作。对于该关系,插入一个链接实体。使用学校 ID 和用户 ID 的组合作为链接实体的 ID。将两个 id 作为单独的属性存储在实体中。例如,用户 '6h30n' 上学 'g3g0a3' 的 SchoolUserLink 获取 id 'g3g0a3~6h30n' 并包含以下字段:school=g3g0a3 和 user=6h30n。使用对学校属性的查询来获取学校的所有 SchoolUserLink。

    原因如下:

    1. 用户会经常看到他们的学校,但很少更改它们。使用这种方法,用户的学校将被缓存,并且不必在每次点击他们的个人资料时都被获取。

    2. 由于您将通过键查找来获取用户的学校,因此您不会使用查询。因此,您不必处理用户学校的最终一致性问题。

    3. 学校可能有很多用户参加。通过将这种关系存储为链接实体,我们可以避免创建一个巨大的单个对象。

    4. 上过学校的用户会有很大的变化。这样我们就不必经常编写单个大型实体。

    5. 通过使用 User 实体的 id 作为 UserSchoolLinks 实体的 id,我们可以只知道用户的 id 来获取链接。

    6. 通过将学校 ID 和用户 ID 组合为 SchoolUser 链接的 ID。我们可以进行密钥查找以查看用户和学校是否链接。再一次,无需担心最终的一致性。

    7. 通过将用户 ID 作为 SchoolUserLink 的属性包含在内,我们不需要解析 SchoolUserLink 对象来获取用户的 ID。我们还可以使用此字段来检查两个方向之间的一致性,并在人们以某种​​方式上数百所学校的情况下进行备用。

    缺点: 1.这种做法违反了DRY原则。似乎这里的邪恶最少。 2. 我们仍然需要使用查询来获取上过学校的用户。这意味着要处理最终的一致性。

    不要忘记更新 UserSchoolLinks 实体并在事务中添加/删除 SchoolUserLink 实体。

    【讨论】:

      【解决方案3】:

      你的问题太复杂了,但我试着解释一下最好的解决方案(我会用 Python 回答,但用 Java 也可以)。

      class User(db.User):
        followers = db.StringListProperty()
      

      简单的添加关注者。

      user = User.get(key)
      user.followers.append(str(followerKey))
      

      这允许快速查询谁被关注和关注者

      User.all().filter('followers', followerKey) # -> followed
      

      这个查询 i/o 成本很高,因此您可以让它更快,但在 i/o 写入方面更复杂且成本更高:

      class User(db.User):
        followers = db.StringListProperty()
        follows = db.StringListProperty()
      

      在更改过程中这很复杂,因为删除用户需要更新,因此您需要 2 次写入。

      您也可以存储关系,但这是更糟糕的情况,因为它比第二个带有关注者和关注者的示例更复杂...... - 请记住,实体可以有 1Mb,它不是限制但可以。

      【讨论】:

      • 您需要两次以上的写入。他将需要每个实体的写入加上列表中每个元素的写入。了解 App Engine 中的编写成本。
      • GAE 数据存储在设计上不是关系数据库,因此请尝试像在关系数据库中那样做关系,这是错误的路径 - GAE 具有高可扩展性和灵活的图形,可以按原样使用它。爆炸的问题是当您在列表属性上创建许多条件时 - 在字段或多个字段上单次拍摄= 没有问题。爆炸与多个字段上的复杂索引有关。如果您使用某些自定义排序或= 以外的某些运算符,则需要此索引。请参阅您管上的视频,他们对此进行了解释 - 这是因为所有组合都必须存储在复杂索引中。
      • 索引列表属性最大的问题是写入成本。
      • 感谢 Chameleon 的回复,但与需要始终加载相同时间的对象相比,在列表中保存数百万个关系不会非常低效吗?您能否论证“存储关系,但情况更糟,因为它更复杂”的说法?
      猜你喜欢
      • 2015-10-08
      • 2013-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-10
      • 2014-03-12
      • 2012-07-09
      相关资源
      最近更新 更多