更新注意更改为聚合键
我要求在匿名环境中从发生少于指定次数的文件列中删除所有值。将指定多个列,要求值在该列中出现超过X次,并且消隐是相对于列的。含义如果X = 4且值123在第1列中出现3次而在第4列中出现4次,则所有3次出现必须在第1列中消隐,但在第4列中是允许的。
我使用2遍方法解决了这个问题,该方法利用过去半年稳定的Hadoop Streaming和Perl。问题是提供了一个新文件,虽然我的流程将处理它,但我们的862容器(54节点)hadoop集群需要大约5天的时间来处理。我将解释我的解决方案/方法,并要求任何适合此问题的优化或替代方法,并允许优化的运行时间。
正在处理的文件统计信息:
此Pass使用带有Perl(v5.10.1)Mapper和Perl reducer的Hadoop流来获取输入文件的每列中的每个值(相对于每列的计数)的计数,并且如果计数是,则构建查找文件更大的指定X包含列,值,计数。
Mapper:使用Perl哈希来构建哈希散列,其中包含列,值,计数,其中每次出现该列的值时计数都会递增:
$ColValCntHash{ $col }{ $value }++;
由于这是使用hadoop流的映射器,我使用以下键值格式打印结果,以允许对reducer进行更多分布式分配:
col|value\tcount
这将生成列#的聚合键和字段值,然后将该列中该值的计数指定为键。
Reducer:读取传递给它的col | value \ tcount,解析密钥,构建一个哈希来汇总从多个映射器传入的值的计数。
$FinalCountHash{ $col }{ $value } += $count;
最后,如果计数小于指定的X,则每个reducer将col,value和count输出到输出文件中。
首次通过结果:第1次传递会产生"查找"源于传递2的列表,其中消隐发生并且结构为:
Col\tValue\tCount
1\t123\t3
3\t234\t2
如果传递1生成的查找文件中存在值,则此传递使用仅具有Perl映射器(无减少阶段)的Hadoop流来清空输入文件中的值。
Mapper:映射器要求将查找文件与分发到每个节点的Perl代码一起发送,以便它可以用于构建查找散列,如:
$aggKey = $col . "|" . $value;
$LookupHash{ $aggKey }=1;
aggKey用于减少Hash of Hashes的内存开销,现在只是一个哈希,它可以将我可以缓冲到内存中的值的数量增加到大约22 mil。
当输入记录通过映射器传递时,迭代列并检查散列以查看列和值是否存在。如果是这样,那么列中的值将替换为空白。
传递2输出: Pass 2生成了一个包含"匿名"的输出文件(部分文件)。输入文件的版本。
尽管集群有足够的内存(2.4TB组合),但它在容器之间分配,并且在容器因内存问题而失败之前,最多可以从查找文件加载大约22mil的值。这需要在输入上进行迭代消隐,方法是将查找文件拆分为22mil块,并在第2遍的多次传递中对主文件运行它们.10-100 GB文件的整个过程在5到20分钟内运行,具体取决于要空白的值的数量。
现在我有一个1.4TB大小的文件来处理这个有近20k列的文件,生成的查找文件包含46亿条记录(大约是文件中总值的1%,这是以前文件的相对百分比) 。
内存限制一次只加载22mil的查找值...这使我当前的进程需要在第2次传递210次传递输入文件..并且必须读取1.4 TB文件(其中)每次移除都会略微收缩),每次通过大约需要25分钟。
我希望这能充分描述问题/当前的解决方案/问题。任何帮助将不胜感激。
谢谢!
答案 0 :(得分:0)
我不了解Hadoop,但内存问题可能是在 Pass 2 中构建%LookupHash
的副产品。
根据问题的描述, Pass 2 不需要知道第1列中值123出现的次数,只需要将值123消隐。
在这种情况下,通过存储数组引用而不是哈希引用,可以使%LookupHash
占用更少的内存。我的测试表明,这应该会减少50%的内存:
my %LookupHash;
while (<>) {
my ( $col, $value ) = split /\t/, $_, 2;
push @{ $LookupHash{$col} }, $value;
}
想想看,需要匹配的值列表是正确的,所以正则表达式:
for my $col ( keys %LookupHash ) {
my $values = join '|', map { '^' . $_. '$' } @{ $LookupHash{$col} };
$LookupHash{$col} = $values;
}
然后使用它应该是一件轻而易举的事:
$value = '' if $value =~ qr/$LookupHash{$col}/;
更好的是,避免完全创建数组引用,而是依赖于手动构建正则表达式字符串(这应该消耗一个数量级更少的内存):
my %LookupHash;
while (<>) {
my ( $col, $value ) = split /\t/, $_, 2;
if ( $LookupHash{$col} ) {
$LookupHash{$col} = join '|', $LookupHash{$col}, $value;
}
else {
$LookupHash{$col} = $value;
}
}
然后使用它:
$value = '' if $value =~ qr/^(?:$LookupHash{$col})$/;