处理数十亿条记录的解决方案,可实现更快的插入和即时检索



我有一个文本文件(称之为grand-parent文件),其中包含100万行。这些行中的每一行都包含一些其他文件(称为父文件)的绝对路径,如下所示。父文件的路径是唯一的。

%: cat input.txt - grand parent file
/root/a/b/c/1.txt  -- parent file1
/root/a/b/c/2.txt  -- parent file2 ......
...
/root/a/b/d/3.txt
......
.....
upto 1 million files.

同样,上面的每个父文件都包含不同文件的绝对路径(称为子文件)及其行号,如下所示:相同的子文件可能存在于具有相同或不同木材的多个父文件中。

%: cat /root/a/b/c/1.txt -- parent file
s1.c,1,2,3,4,5 -- child file and its line numbers
s2.c,1,2,3,4,5....
... 
upto thousands of files
%: cat /root/a/b/c/2.txt
s1.c,3,4,5
s2.c,1,2,3,4,5....
... 
upto thousands of files

现在我的要求是,给定一个子文件和行号,我需要在一分钟内返回所有具有给定子文件号和行号数据的父文件。插入需要在一天内完成。

我创建了一个具有以下模式的关系数据库:

ParentChildMapping - Contains the required relation
ID AUTOINCREMENT PRIMARY KEY
ParentFileName TEXT
ChildFileName TEXT
LNumber INT
For a given file name and line number:
SELECT ParentFileName from ParentChildMapping where ChildFileName="s1.txt" and LNumber=1;

我将grand-parent文件划分为1000个单独的集合,每个集合包含1000条记录。然后我有一个python程序,它解析每个集合,读取父文件的内容并插入到数据库中。我可以创建数千个并行运行的进程,并并行插入所有记录,但我不确定这会对关系数据库产生什么影响,因为我将并行插入数百万条记录。此外,我不确定关系数据库是否是这里选择的正确方法。你能告诉我是否有任何工具或技术更适合这个问题吗。我从sqlite开始,但它不支持并发插入,并且由于数据库锁定错误而失败。现在我想尝试MySQL或任何其他适合这种情况的替代解决方案。

以千个进程并行运行并插入MySQL的示例代码:

import MySQLDb
connection = MySQLDb.connect(host, username,...)
cursor = connection.cursor()
with open(some_set) as fd:
for each_parent_file in fd:
with open(each_parent_file) as parent_fd:
for each_line in parent_fd:
child_file_name, *line_numbers = each_line.strip().split(",") 
insert_items = [(each_parent_file, child_file_name, line_num) for line_num in line_numbers]
cursor.executemany("INSERT INTO ParentChildMapping (ParentFileName, ChildFileName, LineNumber) VALUES %s" %insert_items)
cursor.commit()
cursor.close()
connection.close()

让我们从数据库需要做什么来组织数据的天真想法开始。

你有一百万个父文件。

每一个都包含数千个子文件。比方说10000。

每个都包含一个行号列表。你没有说有多少。比方说100。

这是10^6 * 10^4 * 10^2 = 10^12记录。假设每个是50个字节。这是50 TB的数据。我们需要以某种方式组织它,所以我们对它进行排序。这需要大约40次通过的log_2(10^12)的顺序。这种天真的方法需要2 * 10^15的数据。如果我们在一天86400秒的时间内完成这项工作,则需要我们每秒处理23 GB的数据。

您的硬盘驱动器可能没有50 TB的空间。即使是这样,它的数据流传输速度也可能不会超过500 MB/秒,这是速度的50倍。

我们能改进吗?当然。可能一半的传球都是在记忆中发生的。您可以用12字节元组替换记录。有多种方法可以压缩这些数据。但是通常的";大容量插入数据,创建索引";不会在标准关系数据库方法上为您提供所需的性能。

祝贺你。当人们谈论#bigdata时,他们通常都有小数据。但事实上,你已经足够了,这很重要。

所以。。。你能做什么?

首先,你能用现成的工具做什么?

如果一台计算机没有马力,我们就需要分布式的东西。我们需要像Cassandra这样的分布式密钥/值存储。我们需要像Hadoop或Spark这样的东西来处理数据。

如果我们有这些,我们所需要做的就是处理这些文件,并将它们作为记录加载到Cassandra中,按父文件+子文件,按行号。然后,我们做了一个map reduce,通过子文件+行号来查找父文件中包含的内容,并将其存储回Cassandra中。然后我们通过查询Cassandra得到答案。

但是请记住信封背面所需的数据量和处理量。这种方法允许我们在有一些开销的情况下,以分布式的方式完成所有这些工作。这使我们能够在固定的时间内完成那么多工作并存储那么多数据。然而,你也需要那么多机器来完成这项工作。你可以很容易地从AWS租用这些机器,但你最终也会为它们付费。

好吧,假设你愿意构建一个定制的解决方案,你能做一些更高效的事情吗?也许在一台机器上运行?在你所有的原始数据集都适合一台机器之后,对吧?

是的,但这也需要一些发展。

首先,让我们提高数据的效率。一个显而易见的步骤是为要索引的文件名创建查找表。您已经在列表中有了父文件,这只需要在RocksDB之类的文件中插入一百万条记录即可进行正向查找,反向查找也是如此。您还可以生成所有子文件名的列表(重复),然后使用Unix命令执行sort -u以获得规范的文件名。执行同样的操作,您将获得类似的子文件查找。

接下来,我们之前生成这么多数据的原因是,我们采用了一条类似的线路

s1.c,1,2,3,4,5

并将其转化为:

s1.c,1,/root/a/b/c/1.txt
s1.c,2,/root/a/b/c/1.txt
s1.c,3,/root/a/b/c/1.txt
s1.c,4,/root/a/b/c/1.txt
s1.c,5,/root/a/b/c/1.txt

但是如果我们把s1.c变成一个像42一样的数字,把/root/a/b/c/1.txt变成1,那么我们可以把它变成这样的东西:

42,1,1,5

这意味着子文件42,父文件1从第1行开始,到第5行结束。如果我们对每个字段使用4个字节,那么这就是一个16字节的块。我们每行只生成几个。假设平均为2。(很多行会有一个,其他行可能有多个这样的块。)所以我们的整个数据是200亿16字节的行,对应320 GB的数据。对其进行排序需要34次,其中大部分不需要写入磁盘,而磁盘可以在一台计算机上轻松完成一天的工作。(你所做的是在内存中对1.6GB的块进行排序,然后将它们写回磁盘。然后你可以在8次合并中得到最终结果。)

一旦你有了排序后的文件,你现在可以写下每个文件发生的偏移量。

如果每个子文件都在数千个父文件中,那么解码这就是一个从文件名到子文件ID的查找问题,然后从子文件ID到列出该子文件的范围的查找问题。浏览上千条记录,并形成一个包含数千个在其范围内具有行号的父文件的列表。现在查找它们的名称,并返回结果。这个查找应该在几秒钟内运行,并且(因为所有内容都是只读的)可以与其他查找并行进行。

但这是一个相当数量的软件编写。这就是我要走的路。但是,如果系统只需要使用几次,或者您有其他需求,那么天真的分布式解决方案很可能具有成本效益。

最新更新