我拥有用户订阅主题的数据库。 目前有大约20 000个主题, 存储在SQL数据库中的20万个用户和200毫升订阅。 由于它的大小,数据库按主题划分, 所以我无法在一个数据库查询中获取信息。 有几个主题有10万个订阅,一对有10万,其他有数百或更少。
当一个事件发生时,它通常匹配几个主题,所以为了通知用户,我需要执行类似&#34的查询;给我所有用户订阅主题x,y,z并执行集合的联合" ,即使他订阅了主题x和z,一个用户也能得到一次新闻。
约束是:
我考虑过为每个主题使用一组布隆过滤器,但是他们的限制是相反的:"用户要么没有订阅肯定,要么订阅"。我需要像#34;用户订阅肯定或可能不是"。
有损哈希表可能是个好主意,但我不确定,如果它们可以像布隆过滤器那样具有内存效率,而且我担心它会永远是同一个用户,那就是缺少他主题中的内容。
你知道其他任何数据结构,这对于解决这个问题有用吗?
答案 0 :(得分:2)
如果每个用户记录都有一个代表所有主题的BIT FIELD,那该怎么办。
TABLE用户(ID INT,UserName VARCHAR(16),Topics BINARY(8000))
二进制8k允许您拥有64000个主题。我可能会使用多列BINARY(1024),因此我可以轻松添加更多主题。
现在当一个事件被标记为主题1,10,20,30,40时。 我必须搜索每个用户,但这可以并行化,并且总是N复杂度,其中N是总用户数。
SELECT ID
FROM Users (READPAST)
WHERE
SUBSTRING(Topics, 1 / 8, 1) & (1 * POWER(2, (1 % 8))) > 0
OR
SUBSTRING(Topics, 10 / 8, 1) & (1 * POWER(2, (10 % 8))) > 0
OR
SUBSTRING(Topics, 20 / 8, 1) & (1 * POWER(2, (20 % 8))) > 0
OR
SUBSTRING(Topics, 30 / 8, 1) & (1 * POWER(2, (30 % 8))) > 0
OR
SUBSTRING(Topics, 40 / 8, 1) & (1 * POWER(2, (40 % 8))) > 0
OPTION (MAXDOP = 64)
答案 1 :(得分:2)
正如我在评论中所说,基于记忆的精确解决方案当然是可行的。
但是如果你真的想要一个近似的数据结构,那么你正在寻找一个随机驱逐的(每个主题的用户)大小有限的集合。
您还需要在查询到达时快速计算联合。这里没有有用的预先计算。如果主题集倾向于重复,您可以查看缓存常用的联合。
表示集合的所有常用方法都适用。哈希表(包括已关闭和打开),树和跳过列表(都包含用户ID键;不需要任何值)是最有可能的。
如果使用具有良好散列函数的闭合散列表,则会自动进行伪随机逐出。碰撞时,只需替换以前的值。封闭哈希的问题总是为您需要表示的集合选择一个好的表大小。请记住,要恢复设置元素,您必须遍历整个打开的表,包括空条目,因此从一个巨大的表开始并不是一个好主意;而是从一个小的开始并重新组织,每次增长一个因子,因此重组会分摊每个元素存储的恒定时间开销。
使用其他方案,当表格太大时,你可以逐字地进行伪随机驱逐。公平驱逐的最简单方法是将用户ID存储为一个表,并具有大小有限的集存储索引。通过在表中生成随机索引并在添加新ID之前删除该ID来进行驱逐。
也可以通过使用order statistic tree从BST集表示中公平地逐出:存储每个节点中的后代数。然后你总是可以按键排序顺序找到第n个元素,其中n是伪随机的,并逐出它。
我知道你正在寻找Bloom过滤器的按位空间效率,但保证没有误报似乎排除了这一点。
答案 2 :(得分:1)
这可能不是您正在寻找的解决方案,但您可以使用ElasticSearch的{{3}}并为每个用户提供一个这样的文档:
{
"id": 12345,
"topics": ["Apache", "GitHub", "Programming"]
}
条款过滤器直接响应查询“哪些用户至少订阅了其中一个主题”,ES非常聪明地了解如何缓存和重新利用过滤器。
它不是概率数据结构,但可以非常有效地解决这个问题。您需要使用terms filter来序列化检索可能较大的JSON响应。如有必要,您可以将此解决方案扩展到分散在多台计算机上的数十亿用户,并且响应时间为10 - 100毫秒。您还可以找到主题之间的相关性(重要术语聚合),并使用ES作为进一步分析的引擎。
编辑:我在Python中实现了搜索和扫描/滚动API使用,并获得了一些有趣的结果。我用这个20m用户和200m订阅数据集做了“订阅这三个主题的用户”查询,一般来说,搜索本身在4到8毫秒内完成。查询返回350.000 - 750.000个用户。
即使使用扫描/滚动API,从用户ID中取出问题也会产生问题。在Core i5上,我似乎只有8200个用户/秒,所以它不到50万/分钟("_source": false
)。查询本身如下所示:
{
"filtered": {
"filter": {
"terms": {
"topics": [ 123, 234, 345 ],
"execution": "plain",
"_cache": false
}
}
}
}
在生产中,我会使用"execution": "bool"
,以便可以缓存部分查询结果并在其他查询中重新使用。我不知道结果是什么是瓶颈,服务器的CPU使用率是50%,我使用elasticsearch.helpers.scan
在同一台机器上运行客户端的python脚本。
答案 3 :(得分:1)
[此解决方案与Louis Ricci相似,除了颠倒到话题表 - ,这可能会使订阅更新变得不实用,请注意!]
(概率数据结构方法很酷,但对于您当前的数据大小是不必要的。我最初看的是非概率解决方案的压缩位集,因为它们非常擅长在内存中执行集合操作,但我认为这也有点过分。Here is a good implementation for this type of use-case.如果你有兴趣的话。)
但是查看数据的稀疏性,bitsets会浪费空间而不是整数数组。即使使用整数数组,SELECT
c.id, c.title, SUM(r.views) AS views
FROM
categories AS c
LEFT JOIN
records AS r
ON r.category_id = c.id
GROUP BY
c.id, c.title ;
操作仍然非常便宜,因为每个主题平均只有10,000个订阅。
所以,也许,或许,根据您的用例,简单的数据结构就是:
union
存储(平均)10,000个订户ID(假设32位整数)每个主题只需要 40kb 空间。
[在数组类型或BLOB中,具体取决于您的数据库]
有20,000个主题,这只会为你的主题表添加800mb的数据......当发生通知事件时,需要将很少的这些(~200kb avg)加载到内存中! < / p>
然后,当发生平均事件(影响5个主题)时,所需要做的就是:
查询/将相关主题(平均5条记录)的数据拉入内存 ( avg~200kb 的I / O)
将它们转储到Set数据结构中(重复删除订阅者列表)
在Set中警告订阅者。