【问题标题】:Collision probability of ObjectId vs UUID in a large distributed system大型分布式系统中 ObjectId 与 UUID 的冲突概率
【发布时间】:2014-05-01 15:07:44
【问题描述】:

考虑到 UUID rfc 4122(16 字节)比 MongoDB ObjectId(12 字节)大得多,我试图找出它们的冲突概率比较。

我知道这不太可能,但在我的情况下,大多数 id 将在大量移动客户端中生成,而不是在有限的一组服务器中生成。 我想知道在这种情况下是否存在合理的担忧

与所有 id 由少数客户端生成的正常情况相比:

  • 自文档创建后检测冲突可能需要数月时间
  • ID 由更大的客户群生成
  • 每个客户端的 ID 生成率较低

【问题讨论】:

  • 如果您担心数据完整性,为什么允许移动客户端创建 ObjectId 或任何永久 Id?
  • 客户端可能离线,存储的信息可能长时间不同步。我不想强制 100% 在线移动应用程序
  • @WiredPrairie 大多数 client 库实现默认创建 _id 值。并不是说直接连接是一个“好主意”。但是“ObjectId”生成是完全有效的。
  • 就个人而言,我不会构建或设计允许客户执行此操作的系统。我会在离线时为他们分配临时 ID。我认为这与期望客户端在不通过数据验证层的情况下不直接写入 MongoDb 没有什么不同。
  • 这一切让我重新考虑了 UUID 对离线客户端的作用。 @WiredPrairie 对等待验证层的时间 id 的评论似乎比仅仅依赖 UUID 更好地证明了未来,但实现起来也很痛苦……好吧,分区容错从来都不是小菜一碟。感谢您提到“生日问题”。

标签: mongodb guid uuid rfc4122


【解决方案1】:

在我的情况下,大多数 ID 将在大量移动客户端中生成,而不是在有限的一组服务器中生成。我想知道在这种情况下是否存在合理的担忧。

对我来说,这听起来像是非常糟糕的架构。您使用的是两层架构吗?为什么移动客户端可以直接访问数据库?您真的要依赖基于网络的安全性吗?

无论如何,关于碰撞概率的一些审议:

UUID 和 ObjectId 都不依赖于它们的绝对大小,即两者都不是随机数,但它们遵循一种尝试系统地降低冲突概率的方案。如果是ObjectIds, their structure is

  • 自 unix 纪元以来的 4 字节秒数
  • 3 字节机器 ID
  • 2字节进程ID
  • 3 字节计数器

这意味着,与 UUID 不同,ObjectId 是单调的(除了在一秒钟内),这可能是它们最重要的属性。单调索引将使 B-Tree 被更有效地填充,它允许按 id 分页并允许按 id 进行“默认排序”以使您的游标稳定,当然,它们带有易于提取的时间戳。这些是您应该注意的优化,它们可能是巨大的。

从其他 3 个组件的结构中可以看出,如果您在单个进程上执行 > 1k 插入/秒(实际上不可能,甚至来自服务器),或者如果数量机器的数量超过 10 个(请参阅生日问题),或者如果单台机器上的进程数量增长过大(同样,这些不是随机数,但它们在一台机器上确实是唯一的,但必须缩短它们到两个字节)。

自然,要发生冲突,它们必须在所有这些方面匹配,因此即使两台机器具有相同的机器哈希,它仍然需要客户端插入相同的计数器值在完全相同的秒和相同的进程 id 中,但是是的,这些值可能会发生冲突。

【讨论】:

  • 我们又这样做了。厄运!
  • 移动客户端将无法直接访问数据库,事实上,即使没有连接到数据库,它们也可以运行。但是,每个移动客户端迟早都必须将文档上传到主数据库。
  • 公平地说,我确定我在我的时区倒了酒。没关系,只要问题得到解决。
  • 从客户端生成 ID 存在完全有效的情况,这并不意味着可以访问数据库。如果您这样做,则绝对不能使用 ObjectId,因为如果您有数万或数十万个客户端生成它们,它将产生严重的冲突问题。我不信任 ObjectId,因为它太容易找到案例,即使它们需要特定条件,也可能会发生冲突。
  • @mnemosyn 有用的答案,但是,我不明白为什么每个进程 1k 插入/秒已经成为问题。您会认为计数器在同一秒内对每个“请求”递增 1,并在下一秒开始时重置为零。但是使用 3 个字节,您可以表示比 1k 大得多的数字。我在这里错过了什么?
【解决方案2】:

让我们看一下documentation 中“ObjectId”的规范:

概述

ObjectId 是一个 12 字节的 BSON 类型,构造使用:

  • 一个 4 字节的值,表示自 Unix 纪元以来的秒数,
  • 一个 3 字节的机器标识符,
  • 一个 2 字节的进程 ID,以及
  • 一个 3 字节的计数器,从一个随机值开始。

让我们在“移动客户端”的上下文中考虑这一点。

注意:这里的上下文是指使用“移动客户端”到数据库的“直接”连接。那应该这样做。但是“_id”生成可以非常简单地完成。

所以要点:

  1. “自纪元以来的秒数”的值。每个请求将是相当随机的。因此,仅对该组件的碰撞影响最小。尽管在“几秒钟内”。

  2. “机器标识符”。所以这个一个生成_id值的不同客户端。这消除了进一步“碰撞”的可能性。

  3. “进程 ID”。因此,在种子可以访问的地方(应该是),那么生成的_id更多避免碰撞的机会。

  4. “随机值”。因此,另一个“客户”设法生成了所有与上述相同的值,并且仍然设法生成了相同的随机值。

底线是,如果 that 不足以令人信服地消化,那么只需提供您自己的“uuid”条目作为“主键”值。

但是恕我直言,这应该是一个公平令人信服的论据,以考虑这里的碰撞方面非常广泛。至少可以这么说。

full 主题可能只是有点“过于宽泛”。但我希望这能将考虑从“不太可能”转移到更具体的事情上。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-06
    • 2016-10-28
    • 2010-12-24
    • 2020-10-21
    • 1970-01-01
    • 2017-09-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多