对大数据 XML 文件进行排序

Posted

技术标签:

【中文标题】对大数据 XML 文件进行排序【英文标题】:Sorting BIG Data XML file 【发布时间】:2016-08-23 19:13:29 【问题描述】:

我有一个压缩大小约为 100 GB(未压缩 1 TB)的 XML 文件。该文件包含大约 1 亿个条目,方式如下:

<root>
  <entry>
    <id>1234</id>
     ...
  </entry>
  <entry>
    <id>1230</id>
    ...
  </entry
</root>

我想按 id 排序这个文件。这样做的好方法是什么?

顺便说一句,我可以使用 16 核和 128 GB RAM 的机器。

【问题讨论】:

一个好办法是避免排序,为什么需要排序? 对不起,这是机密信息。但我真的需要为另一个大数据应用程序排序这个文件。 您大约有多少条目?一个条目有多大? 我认为您不应该在单个大文件中对其进行处理。你应该把它分成几块,一次处理一个块。 我有大约 1 亿个条目,平均一个条目大约 10,000 字节(未压缩)。 【参考方案1】:

您可以考虑使用像 Saxon http://www.saxonica.com/html/documentation/sourcedocs/streaming/ 这样的流处理器并使用 XSLT 进行排序。

另一种选择可能是将数据作为键、值存储在数据库中,使用 SQL 对它们进行排序并重新创建 XML。您将利用 DB 的强大功能来管理大量数据。

类似问题(不一样):Sort multigigabyte xml file

【讨论】:

Saxon 和 XSLT 如何处理不适合主内存的文件? 我认为 DB 不合适,因为对磁盘的 IO 操作真的很慢(大约每秒 100 次)。 @David 您的数据无法放入您的内存中,因此您实际上没有选择余地,您需要以某种方式持久化您的转换,而数据库在您的情况下似乎是一个不错的选择。像 mongodb 这样的 NoSQL 数据库 也许我应该更准确一点:我不介意按顺序将大量数据写入磁盘。不过,我确实介意随机 IO 操作。【参考方案2】:

因为值(即id)是自然数,对它们进行排序的最佳算法是计数排序 具有 TETA(n) 时间顺序。

假设值在 [1 .. k] 范围内

计数排序>

温度:C[1..k]

输入:A[1..n]

输出:B[1..n]

CountingSort (A, B, k)

   for(i=1 to k) C[i]=0;
   for(i=1 to n) C[A[i]]++;
   for(i=2 to k) C[i]=C[i]+C[i-1];
   for(i=n downto 1)
   
      B[C[A[i]]] = A[i];
      C[A[i]]--;
   

这个算法是稳定的

您还可以使用 Radix Sort 以相同的顺序。

【讨论】:

感谢您的回答。我想过类似的事情:使用计数排序/箱将大文件拆分为许多较小的文件,分别对每个文件进行排序,最后合并所有文件。您对此有什么想法/建议吗? 这是个好主意。 1. 将文件拆分为适合主内存的较小文件。 2. 使用计数排序或基数排序对每个新文件进行排序 3. 将所有文件合并到一个文件中。【参考方案3】:

在这个阶段,记住人们用来对磁带或穿孔卡片进行分类的技术是很有用的,当时数据远大于可用的直接存取存储器。 (我曾经看过一组操作员对 25 万张卡片进行分类 - 大约 120 个托盘)。您基本上需要流式传输、合并和拆分的组合,这些操作原则上都可以使用 XSLT 3.0。有两种处理器可用,Saxon-EE 和 Exselt,但都不是 100% 完整的实现,因此您会受到产品限制而不是规范的限制。

我的直觉是逐位排序。您没有说 id 作为排序键使用了多长时间。这里的“Digits”当然不一定是十进制数字,但是为了简单起见假设十进制,基本思路是先根据排序键的最后一位将文件分成10个桶,然后处理桶中的基于此排序的序列,这次按倒数第二个数字排序,并继续执行与键中一样多的数字:对排序键中的每个数字进行一次完整文件的传递。

如果 id 很密集,那么大概有 100m 个密钥,它们大约有 8 位长,这将是 8 次传递,如果我们假设处理速度为 10Gb/min,这可能是您可以从现成得到的最好的 -架 XML 解析器,那么 1Tb 文件的每次传递将需要 2 小时,因此 8 次传递将是 16 小时。但是使用 base-100 可能会更好,这样您在每次传递时拆分为 100 个文件,然后您只有 4 次传递。

基本的 XSLT 3.0 代码是:

<xsl:stream href="in.xml">
 <xsl:fork>
  <xsl:for-each-group select="record" 
       group-by="substring(key, $digit, 1)">
   <xsl:result-document href="tempcurrent-grouping-key()">
     <xsl:sequence select="current-group()"/>
   </xsl:result-document>
 </xsl:for-each-group>
</xsl:fork>

现在有个坏消息:在 Saxon-EE 9.7 中,此代码可能没有得到充分优化。虽然原则上每个组中的项目都可以直接流式传输到相关的序列化结果文档,但 Saxon 还没有特别处理这种情况,并且会在处理之前在内存中构建每个组。不知道Exselt能不能做得更好。

那么还有其他选择吗?好吧,也许我们可以尝试这样的事情:

    将文件拆分为 N 个文件:即将前 X/N 项放入文件 1,下一个 X/N 项放入文件 2,依此类推。 对每个文件进行排序,通常在内存中。 使用 xsl:merge 对结果文件进行流式合并。

我认为这适用于撒克逊。第一步可以使用在撒克逊完全流式传输的&lt;xsl:for-each-group group-adjacent="(position()-1) idiv $N"&gt; 完成。

这本质上是一个 3-pass 解决方案,因为每个项目都被解析和序列化 3 次。我会将 1Tb 文件拆分为 100 个 10Gb 文件。做一个 10Gb 的内存 XSLT 正在推动它,但你有一些马力可以玩。但是,您可能会遇到 Java 寻址限制:我认为数组和字符串有 1G 的限制。

【讨论】:

以上是关于对大数据 XML 文件进行排序的主要内容,如果未能解决你的问题,请参考以下文章

对大文件排序

使用 MapReduce/Hadoop 对大数据进行排序

可以对大文件排序的排序算法

按所需顺序对大文件进行排序

如何在 Python 中对大文本文件流进行过滤和排序

核心数据。对大尺寸 NSManagedObject 的 NSMutableSet 进行排序