【问题标题】:Random exhaustive (non-repeating) selection from a large pool of entries从大量条目中随机详尽(非重复)选择
【发布时间】:2012-07-13 03:24:42
【问题描述】:

假设我有大量 (300-500k) 的文本文档集合存储在关系数据库中。每个文档可以属于一个或多个(最多六个)类别。我需要用户能够随机选择特定类别中的文档,这样单个实体就不会重复,就像 StumbleUpon 的工作方式一样。

我真的看不出有一种方法可以使用带有大量用户和文档的慢速 NOT IN 查询来实现这一点,所以我想我可能需要为此目的实现一些自定义数据结构。也许已经有一篇论文描述了一些可能适合我需要的算法?

目前我正在考虑以下方法:

  • 从数据库中读取所有条目
  • 根据属于该类别的文档的 ID,为每个类别创建一个基于链表的索引。随机播放
  • 创建一个包含特定用户查看的所有条目的布隆过滤器
  • 使用迭代器遍历索引,使用布隆过滤器随机选择项目以挑选未查看的项目。

【问题讨论】:

  • 集合会随着时间的推移而增长,还是固定的?此外,您是否希望任何给定用户对类别中的文档进行不同的随机化,或者不同的用户可以看到相同的随机排序? (我认为这不好,但您正在考虑的方法似乎涉及每个类别的固定随机化......?)

标签: algorithm data-structures random indexing


【解决方案1】:

如果您通过表格跟踪用户看到的条目...试试这个。我将使用 mysql,因为这是我能想到的最快的例子,但要点应该很清楚。

关于正在“使用”的链接...

insert into viewed (userid, url_id) values ("jj", 123)

正在寻找链接...

select p.url_id
from pages p left join viewed v on v.url_id = p.url_id
where v.url_id is null
order by rand()
limit 1

这会导致数据库继续执行 1 对 1 连接,并且您将查询限制为仅返回用户尚未看到的一个条目。

只是一个建议。

编辑:可以进行这一操作,但不能保证 url 会成功传递给用户。

【讨论】:

  • 与我最初认为的反连接解决方​​案相反。再加上抢先获取和缓存,这应该可以解决问题。顺便说一句,“不能保证 url 会成功传递给用户”是什么意思。
  • 如果您将其设为一项操作,即调用返回 url 并将其标记为“已使用”的数据库,则数据库的工作已完成,由其他组件来确保信息被使用。例如:如果您的程序调用它然后崩溃,500 错误或什么不是......数据库仍然认为这是一个好的调用,即使您稍后修复 500,该 url 也不会显示。
【解决方案2】:

这取决于用户如何获得它的随机条目。

选项 1:

用户正在对一些实体进行分页,并在其中几个之后停止。例如,用户看到当前的随机实体,然后移动到下一个,阅读它并继续它几次,就是这样。 下次该用户(或其他用户)从该类别中获取实体时,已查看的实体清晰可见,您可以返回已查看的实体。

在该选项中,我建议保存一组(散列)已查看的实体 ID,并且每次用户要求随机实体时 - 从数据库中随机选择它并检查是否尚未在集合中。

因为集合太小而你的数据太大,你得到一个已经查看过的id的机会太小了,大部分时间都需要O(1)。

选项 2:

用户正在实体中分页,并且查看的实体正在所有用户之间以及每次用户访问您的页面时保存。 在这种情况下,您可能会使用每个类别中的所有实体并保存所有查看过的实体 + 检查是否查看过实体将需要一些时间。

在该选项中,我将获取该主题的所有 id - 将它们打乱并将其存储在链接列表中。当您想获得一个随机未查看的实体时 - 只需获取列表的头部并将其删除 (O(1))。

【讨论】:

    【解决方案3】:

    我假设对于任何给定的 对,查看的文档数量相对于该类别中可用的文档总数来说非常小。

    那么你可以只存储索引三元组 指示哪些文档已被查看,然后对随机选择的文档采取乐观的方法吗?在绝大多数情况下,用户不会阅读随机选择的文档。而且您可以快速检查,因为三元组已编入索引。

    【讨论】:

    • 就像我说的,一个文档可以属于多个类别,就像SO question可以有很多标签一样。而且我相信一旦用户查看了许多项目,性能会迅速下降。我还不如只使用 NOT IN 查询。
    • 这样的算法在很大程度上取决于您所做的假设。你是对的,随着用户阅读越来越多的文档,性能会下降。但我再次假设每个类别都有数以万计的文档,并且大多数用户阅读的文档远远少于任何给定类别中可用的 50K+ 文档。这些假设是错误的吗?
    • 这可能有效,但我相信可能有更有效的解决方案
    • 我对此没有任何严重的怀疑,但请记住空间/时间的权衡。在合理的假设下,我所描述的解决方案几乎总是涉及一次选择和一次索引检查(良好的时间性能),我相信在空间上它涉及存储所需的最少信息量。问题是,如果许多用户违反假设,它就会开始崩溃。 (如果有少数人这样做,没什么大不了的。)为每个用户类别存储随机列表可以为您带来更好的时间可伸缩性,但空间成本是多少?
    【解决方案4】:

    我会选择伪随机方法:

    1.) 确定要查看的类别中的元素数量 (SELECT COUNT(*) WHERE ...)
    2.) 在范围 1 ... 中选择一个随机数。
    3.) 选择单个文档(SELECT * FROM ... WHERE [与计数时相同] ORDER BY [生成稳定顺序]。根据使用的 SQL 方言,有不同的子句可用于仅检索部分您想要的结果集(MySQL LIMIT 子句、SQLServer TOP 子句等)

    如果文档的数量很大,那么两次为同一用户提供同一文档的机会很小,可以忽略不计。使用上述方案,您根本不必存储任何状态信息。

    【讨论】:

      【解决方案5】:

      您可能需要考虑像 Apache Cassandra 这样的 nosql 解决方案。这些似乎非常适合您的需求。有很多方法可以在一个环境中设计您需要的算法,您可以轻松地在运行中向表(列族)添加新列,并为非常稀疏的表提供出色的支持。

      编辑:以下许多可能的解决方案之一:

      1. 为每个类别创建一个 CF(列族,即表)(即时创建这些非常容易)。
      2. 为属于该类别的每个文档在每个类别 CF 中添加一行。
      3. 每当用户点击一个文档时,您都会添加一个带有named 的列并将其设置为true 到该行。显然,这张表会很大,有数百万列,而且可能非常稀疏,但没问题,读取这仍然是恒定的时间。
      4. 现在为某个类别中的用户查找新文档只需从 select * where == null 中选择任何结果即可。

      如果您可以接受 Cassandra 的“最终一致”模型(即,用户永远不会获得重复文档并不是关键任务),您应该获得恒定的写入和读取时间、惊人的可扩展性等。

      【讨论】:

      • 能否详细说明一下实现?
      【解决方案6】:

      过去我通过使用Apache Lucene 将关系数据库索引到面向文档的表单中解决了类似问题。这是在 NoSQL 服务器最近兴起之前,基本上是一样的,但它仍然是一种有效的替代方法。

      您将使用 textId(关系数据库 id)字段和多值 categoryId 和 userId 字段为您的每个文本创建一个 Lucene Document。适当地填充 categoryId 字段。当用户阅读文本时,将他们的 id 添加到 userId 字段。一个简单的查询将返回具有给定 categoryId 而没有给定 userId 的文档集 - 随机选择一个并显示它。

      【讨论】:

        【解决方案7】:
        1. 将用户过去 X 次选择存储在 cookie 或其他东西中。
        2. 使用用户的新标准将最后的选择返回给服务器
        3. 随机选择满足条件的文本之一,直到它不是用户最后 X 个选择的成员。
        4. 返回此文本选择并更新最后 X 个选择的列表。

        我会尝试找到 X 的最佳值,但我想的是像 16 之类的 X?

        【讨论】:

          猜你喜欢
          • 2017-10-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-11
          • 1970-01-01
          • 2020-12-01
          • 2011-01-25
          相关资源
          最近更新 更多