Random 如何从CouchDB加载随机文档(高效且公平)?

Random 如何从CouchDB加载随机文档(高效且公平)?,random,couchdb,Random,Couchdb,我想从CouchDB数据库中存储的一组文档中加载一个随机文档。文件的挑选和装载方法应符合以下要求: 效率:文档的查找应该是高效的,最重要的是,加载文档的时间不能随文档总数线性增长。这意味着不能使用skip查询参数 均匀分布:选择应该是真正随机的(尽可能使用标准随机数生成器),每个文档都应该有平等的被选择机会 在CouchDB中实现这一点的最佳方法是什么?在仔细考虑之后,我想出了一个解决方案。为了完整性起见,我将首先展示两种简单的方法,并解释它们存在缺陷的原因。第三个解决方案是我要采用的方案

我想从CouchDB数据库中存储的一组文档中加载一个随机文档。文件的挑选和装载方法应符合以下要求:

  • 效率:文档的查找应该是高效的,最重要的是,加载文档的时间不能随文档总数线性增长。这意味着不能使用skip查询参数

  • 均匀分布:选择应该是真正随机的(尽可能使用标准随机数生成器),每个文档都应该有平等的被选择机会


在CouchDB中实现这一点的最佳方法是什么?

在仔细考虑之后,我想出了一个解决方案。为了完整性起见,我将首先展示两种简单的方法,并解释它们存在缺陷的原因。第三个解决方案是我要采用的方案

方法1:跳过 这是一个简单的解决方案:您有一个简单的视图(我们称之为
random
),其中包含一个map函数,该函数将发送您想要从中选择的所有文档以及内置的
\u count
reduce函数。要随机选取文档,请执行以下步骤:

  • 通过调用:
    http://localhost:5984/db/_design/d/_view/random
  • 选择随机数
    0=0)

    http://localhost:5984/db/_design/d/_view/random?startkey=r&skip=s&limit=1&reduce=false

  • 如果(s如果插入性能不是一个问题,您可以尝试将数字设为非随机数,例如在创建时将其设为doc_count+1。然后您可以使用随机数0来查找它,“滥用”视图的reduce函数如何

    function (keys, values, reduce) {
        if (reduce)
          return values[Math.floor(Math.random()*values.length)];
        else
          return values;
    }
    

    我同意@meliodas:

    以下是选项2(n=1000)的分布:

    使用一半的时间交换开始键/结束键:

    { 0.2: 572,
      0.9: 428 }
    
    不确定当您查看更多数据时,分布会发生什么变化,但一开始似乎更有希望。这完全没有使用选项1,我认为这是不必要的。

    方法2b:文档中的序列号 此方法与中提到的方法2类似。方法2使用随机数两次(一次在文档本身中,一次在拾取文档的过程中)。此方法2b将仅在拾取过程中使用随机数,并在文档中使用顺序整数。请注意,如果删除文档,则此方法将不起作用(见下文)。以下是它的工作原理:

    在创建时向文档中添加顺序整数:

    {
        _id: "4f12782c39474fd0a498126c0400708c",
        int_id : 0,
        // actual data...
    }
    
    另一个医生

    {
        _id: "a498126c0400708c4f12782c39474fd0",
        int_id : 1,
        // actual data...
    }
    
    每一份文件都要加一

    视图
    random
    具有相同的映射功能(尽管您可能希望将其名称更改为“random”以外的名称):

    以下是加载随机文档的步骤:

    {
      _id: "4f12782c39474fd0a498126c0400708c",
      rand: 0.4591819887660398,
      // actual data...
    }
    
    • 通过调用:
      http://localhost:5984/db/_design/d/_view/random

    • 选择随机数
      0这是可能的,但正如您所说,同步文档创建可能会成为一个问题-尤其是如果您希望跨多个CouchDB实例进行集群。删除文档时需要做更多的工作。总之,我认为我的“方法3”更可行。不过,您的输入是+1。谢谢!非常有趣!如果您使用#2,但每次选择一个文档时,您都会更改它的随机数设置。这仍然是不公平的(单个选择的概率分布不均匀),但效果可能会在许多选择中被抵消,因此在实践中可能已经足够好了。您必须将每次查找后存储新版本的性能影响与我的方法#3进行比较。您是否考虑过用文档在视图中生成随机数而不是存储随机数?例如:
      function(doc){emit(Math.random(),null)}
      @NikitaVolkov这种想法的问题似乎是CouchDB在运行查询后会缓存查询。因此,即使您正在运行一个临时动态查询,如果您再次发送相同的查询,它也会返回相同的结果顺序。但是,如果任何字符不同(我对随机字符串进行了任意变量赋值),您返回的结果将是随机的。我认为#2可以工作,如果我们稍微调整一下的话。我们没有将
      r
      设置为
      startkey
      ,而是将其设置为
      startkey
      endkey
      ,基于每个查询随机抛硬币。我认为它通过了0.2和0.9示例。我认为这只适用于此琐事如果你有三个随机值为0.499、0.5、0.501的文件,中间的一个将有很低的被选中的机会,不管你从哪个方面来。你介意尝试一下并分享结果吗?我想你对那个特定的测试集是正确的。我认为讨论的要点是在数据库上实现一个公平的算法其他一切都是黑客行为,都会有缺陷。不过当我本周晚些时候有机会时,我很乐意运行结果。@ChristianBerg当你在所有三个文档中随机设置
      rand
      属性时,这种方法应该有效。在随机分布中,几乎没有ch得到三个接近0.499,0.5,0.501的值的概率。我认为随机分布应该以等距的方式放置所有点,这应该足够了。如果我错了,请纠正我?
      {
          _id: "4f12782c39474fd0a498126c0400708c",
          int_id : 0,
          // actual data...
      }
      
      {
          _id: "a498126c0400708c4f12782c39474fd0",
          int_id : 1,
          // actual data...
      }
      
       function(doc) {
         if (doc.int_id) {
           emit(doc.int_id, doc);
         }
       }