这是一个有趣的问题,我认为 Redis 可以在这里提供帮助。
Redis 可以使用优化的“intset”格式存储整数集。有关更多信息,请参阅http://redis.io/topics/memory-optimization。
我相信这里正确的数据结构是目标标签集的集合,加上将标签映射到目标标签集的反向索引。
存储两个目标标签集:
0 -> [ 1 2 3 4 5 6 7 8 ]
1 -> [ 6 7 8 9 10 ]
我会使用:
# Targeted tag sets
sadd tgt:0 1 2 3 4 5 6 7 8
sadd tgt:1 2 6 7 8 9 10
# Reverse index
sadd tag:0 0
sadd tag:1 0
sadd tag:2 0 1
sadd tag:3 0
sadd tag:4 0
sadd tag:5 0
sadd tag:6 0 1
sadd tag:7 0 1
sadd tag:8 0 1
sadd tag:9 1
sadd tag:10 1
当从系统中添加/删除目标标签集时,这个反向索引很容易维护。
全局内存消耗取决于多个目标标签集共有的标签数量。在 Redis 中存储伪数据并模拟内存消耗非常容易。我使用简单的 node.js 脚本完成了它。
对于 100 万个目标标签集(标签为 8 位数字,每组 40 个标签),当目标标签集共享的标签很少(反向索引中超过 32M 条目)时,内存消耗接近4 GB ,当标签被大量共享时,大约500 MB(反向索引中只有 100K 条目)。
使用这种数据结构,查找包含给定客户的所有标签的目标标签集非常有效。
1- Get customer tag set (suppose it is 1 2 3 4)
2- SINTER tag:1 tag:2 tag:3 tag:4
=> result is a list of targeted tag sets having all the tags of the customer
交集操作是高效的,因为 Redis 足够聪明,可以按基数对集合进行排序,并从具有最低基数的集合开始。
现在我知道您需要实现相反的操作(即找到目标标签集,其所有标签都包含在客户标签集中)。反向索引仍然可以提供帮助。
这是一个丑陋的伪代码示例:
1- Get customer tag set (suppose it is 1 2 3 4)
2- SUNIONSTORE tmp tag:1 tag:2 tag:3 tag:4
=> result is a list of targeted tag sets having at least one tag in common with the customer
3- For t in tmp (iterating on the selected targeted tag sets)
n = SCARD tgt:t (cardinality of the targeted tag sets)
intersect = SINTER customer tgt:t
if n == len(intersect), this targeted tag set matches
因此,您无需针对 1M 目标标签集测试客户标签集。您可以依靠反向索引将搜索范围限制在可接受的范围内。