计算巨大数据集上的令牌计数器

时间:2010-10-11 14:36:07

标签: performance nosql large-data-volumes key-value-store

我需要查看大量文本(> 2 Tb,Wikipedia完全转储)并为每个看到的令牌保留两个计数器(每个计数器根据当前事件递增) 。我需要这些计数器的唯一操作是增加。在第二阶段,我应该根据这些计数器计算两个浮点并存储它们。

它应该执行以下步骤:

  1. 查看大量文字,并为找到的每个单词增加两个计数器,具体取决于当前事件。
  2. 浏览所有令牌,为每个令牌,根据这些计数器计算两个额外的浮点数。
  3. 允许查询(获取任何给定令牌的值)。
  4. 要求和其他细节:

    • 必须扩展到O(10 ^ 8)个令牌。
    • 最终结果需要非常快速地查询!
    • 在翻阅文本时,只会增加两个计数器。这是一次性处理,因此在处理过程中不会有任何疑问。只有价值更新。
    • 无需动态/可更新架构。

    我一直在尝试使用CouchDB和MongoDB而没有太好的结果。

    您认为解决此问题的最佳方法是什么?

    谢谢!

    编辑1:我被建议尝试Patricia trie并测试所有密钥是否适合内存(我怀疑他们没有)。定制Patricia trie带有额外的运算符,可以在一个步骤中增加每个键的值,这可能是一个可能的解决方案。

    编辑2:澄清“巨大”的含义:> 2 Tb的文字。更多澄清。

    编辑3:唯一令牌估算。正如Mike Dunlavey所建议的,我试图快速估计独特的令牌。在数据集的第一个830Mb中,唯一令牌线性增长到52134.除非处理更多数据(很可能)后唯一令牌的数量变慢,否则应该有O(10 ^ 8)个唯一令牌。

    编辑4:首选Java和Python解决方案,但也可以使用其他任何语言。

    编辑5:通常,令牌只包含可打印的ASCII字符,但它们可以包含任何可打印的Unicode字符。我将尝试相同的过程,无论是大写还是大写;仅适用于小写。

6 个答案:

答案 0 :(得分:1)

策略,而不是解决方案;

一个进程没有逃避输入数据的读取,即我没有看到如何并行化初始操作,除非文件在并行I / O系统上,即便如此我认为可能很难并行处理7z文件。

但是,您可以尝试的是实现一个进程,该进程读取输入数据并在文件系统中写入它的块,最好是放在足够不同的磁盘上,以便下一步要启动的进程不会全部排入队列相同的读/写头。

一旦编写了第一个块,你就可以在另一个核心上启动一个进程(你已经拥有了多核还没有你?甚至可能是工作站的集群或网络?)来开始消化那个块。此过程将部分结果写入文件。

一旦第二个块写入,你就会在另一个核心上启动一个进程......

...你得到了照片

完成整个输入后,您可以设计任务以合并处理每个块的任务输出的结果。你可以在某种级联中执行此操作(例如,如果你有32个块和16个处理器,你可能每个合并2个块,那么其中8个合并2个合并的块,依此类推)。

我最好的猜测是你应该对平面文件没问题,不确定数据库的额外功能是否值得额外的成本(在性能和编程的复杂性方面)。我想您可能希望将最终结果写入数据库以支持查询。

编辑:好吧,如果您的所有查询都是“让我获得令牌XXX的计数器”的形式,那么您可以通过单个已排序的文本文件进行二进制搜索。我并不是说你应该这样做,但它可能会指向你的方向。暂时忘记令牌可能以任何字符(这只是字母表的问题)开始,你可以有26个文件,一个用于以A开头的令牌,一个用于以B开头的令牌,等等。

或者您可以在主文件中构造索引,其中包含A(从文件开头偏移0)B(从开始偏移12456)的条目,依此类推。

就个人而言,我会尝试使用one-sorted-text-file-per-initial-letter方法,直到我有一个可行的解决方案,然后弄清楚它是否足够快。但是我可以访问带有大量磁盘和RAM绑定的大型集群,而您的平台可能需要另一种可能更复杂的方法。

答案 1 :(得分:1)

据我所知,你只想算一些代币。第一种解决方案可能只是在内存中使用哈希映射。 52-100k令牌(英语单词的优势长度为大约5.1)+每个令牌4字节用于保持计数的数据并不多。您可以轻松地将地图存储在开发人员计算机的内存中。

第二个解决方案是使用apache lucene来存储新的令牌 - 除非你没有1M条目,你不需要分区索引 - 和我将存储在数据库中的计数器值,例如,sqllite (因为更新lucene索引不是最好的主意)。

为了加快这两个解决方案的流程,我只需将数据集拆分为k * 100数据集,然后在不同的机器上(或并行)分别运行它们,然后合并它们的结果。你的计数结果,你可以毫无问题地总结。

您的用例是apache hadoop教程中的经典示例,但我认为部署它会过度工程化。

答案 2 :(得分:1)

如果你有大量的内存,你可以使用普通的redis来存储计数器(10 ^ 8个独特的令牌,每个计数器大约需要12GB,我猜测。)

如果你没有那么多内存,你仍然可以使用redis,但是使用一些哈希策略和vm_enabled使它适合内存:

您可以将标记除以第一个和第二个字母(aa,ab,ac ... zz)作为哈希名称,将实际单词+标记标识符作为哈希键,将counte作为值。它看起来像这样:

hash ab
- absence_c1 5
- absence_c2 2
- abandon_c1 2
- abandon_c1 10
hash st
- stack_c1 10
- stack_c2 14

但是在这种方法中,由于redis不能在哈希上“加入”,你将获得前一个值并将它们设置为incr并将其设置回来,这样(伪代码):

var last = redis("hget st stack_c1")
var actual = last + 1
redis("hset st stack_c1 actual")

使用此哈希模式并使用启用vm的redis将使内存使用率保持在较低水平,同时仍然足够快。我能够存储2百万个令牌,每个令牌有15个字符,少用100MB的RAM和几乎4G的磁盘。

答案 3 :(得分:1)

高级解决方案:

  1. 通过输入解析,输出“[token] + X + Y”行到1个N输出文件(这些“分片”输出文件中的每一个都足够小,可以在内存中处理。)
  2. [对于每个文件]将其读入内存,输出带有“[token] [count1] [count2] ...”行的排序文件
  3. 在查询时,对正确的文件进行二进制搜索
  4. 详细说明: 这是步骤1)的Python伪代码

    NUM_SHARDS = 1000  # big enough to make each file fit in memory  
    output_files = [open("file" + str(n), "w") for n in xrange(NUM_SHARDS)]
    for token in input_stream:
       shard_id = hash(token) % NUM_SHARDS
       output_files[shard_id].write(token + " +0 +1\n")
       # TODO: output the correct +X and +Y as needed
    

    这是步骤2的Python伪代码

    input_files = [open("file" + str(n)) for n in xrange(NUM_SHARDS)]
    for file in input_files:
       counts = {}   # Key: token   Value: { "count1": 0, "count2": 1 }
    
       # read the file, and populate 'counts'
       for line in file:
          (token, count1, count2) = line.split(" ")
          # make sure we have a value for this token
          counts.setdefault(token, { "count1": 0, "count2": 0 })
          counts[token]["count1"] += int(count1)
          counts[token]["count2"] += int(count2)
          # TODO: compute those floats, and stuff those inside 'counts' also
    
       # now write 'counts' out to a file (in sorted order)
       output_file = open(file.name + ".index", "w")
       for token, token_counts in sorted(counts.items()):
          output_file.write(token + " " + token_counts["counts1"] + " " + token_counts["counts2"] + "\n")
          # TODO: also write out those floats in the same line
    

    以下是步骤3的一些Python代码:

    # assume 'token' contains the token you want to find
    shard_id = hash(token) % NUM_SHARDS
    filename = "file" + str(shard_id) + ".index"
    binary_search(token, open(filename), 0, os.path.getsize(filename))
    
    # print out the line in 'file' whose first token is 'token'
    # begin/end always point to the start of a line
    def binary_search(token, file, begin, end):
        # If we're close, just do brute force
        if end - begin < 10000:
                file.seek(begin)
                while file.tell() < end:
                        line = file.readline()
                        cur_token = line.strip().split(" ")[0]
                        if cur_token == token:
                                print line
                                return True
                return False  # not found
    
        # If we're not close, pivot based on a line near the middle
        file.seek((begin + end) / 2)
        partial_line = file.readline()  # ignore the first fractional line
        line = file.readline()
    
        cur_token = line.strip().split(" ")[0]
        if cur_token == token:
                print line
                return True
        elif cur_token < token:
                return binary_search(token, file, file.tell(), end)
        else:  # cur_token > token
                return binary_search(token, file, begin, file.tell() - len(line))
    

答案 4 :(得分:1)

好的,如果MongoDB和CouchDB不适合你,那么你基本上就会遇到一个问题:功能不够

让我们看一下清单:

  

必须扩展到O(10 ^ 8)个令牌。

你有多少内存?您正在谈论数亿个令牌,而您正在谈论流式传输7zip文件。如果你想快速发出“增量”,你需要能够将整个数据结构保存在内存中,否则整个过程将非常缓慢。

  

最终结果需要非常快速地查询!

多快?微秒,毫秒,几百毫秒?如果你想在具有8GB RAM的机器上查询500M记录,那么你几乎已经被哄骗了。数据不适合,无论您正在使用什么数据库。

  

数据集&gt; 2TB

好的,我们假设你的计算机可以平均大约50MB /秒的持续吞吐量,你的proc可以按照这个速度实际解压缩数据。按照这种速度,您只需要11个小时的处理时间来传输数据(您希望在周末完成此操作吗?)

50小时/秒的吞吐量11小时不是小土豆,这是一个真正的驱动器。如果你在发生这种情况时尝试向磁盘写入任何内容(或者操作系统交换),那么这将会快速降级。

从数据库的角度来看, MongoDB 可以同时处理前端更新和后端查询。但它需要每分钟左右刷新一次磁盘,这将大大延长你的11小时运行时间。

除非你能够处理内存中的整个数据库内存中的整个数据流,否则总的运行时间会变得越来越糟。

我的观点......

很简单,你需要更多的力量。

如果您没有使用24GB + RAM运行此操作,那么您所做的一切都会感觉很慢。如果你没有24GB +的RAM,那么你的最终数据集将不会是“闪电般快速”,最多只能是“200 ms-quick”。您只需索引500M行并期望找到一个条目,除非您可以在RAM中保留索引。

如果您没有使用真棒硬盘驱动器运行此操作,那么操作似乎会很慢。我的意思是,你说的是高通量持续读取(可能是写入)的数小时和数小时。

我知道你需要帮助,我知道你已经对这个问题给予了赏识,但是很难解决以下问题:

  

我一直在尝试使用CouchDB和MongoDB而没有太好的结果。

当听起来你没有真正找到合适的装备解决问题​​时。

答案 5 :(得分:0)

您必须使用数据库,而不是阅读文本文件吗?

一个简单的C类型编译语言可以在读取文件的一小部分时间内运行一个简单的解析器,因此它基本上应该是“I / O绑定”。 它将是一个类似于unix wc,word-count。

的程序

听起来数学是微不足道的,甚至不应该引人注意。

编辑:好的,我不明白你想要建立一个独特令牌的字典,并计算每一个。在这种情况下,基于特里或基于散列的字典就足够了。存储大小取决于令牌的典型长度以及有多少不同的令牌。这可能类似于unix sort | uniq习语。