对于一个项目,我需要解析一些非常大的CSV文件。某些条目的内容存储在MySQL数据库中。我试图使用多线程来提高速度,但到目前为止,这只能减慢速度。
我解析一个CSV文件(最多10GB),其中一些记录(20M +记录CSV中的aprox.5M)需要插入MySQL数据库。要确定需要插入哪条记录,我们使用Redis服务器,其中的集合包含正确的id / references。
由于我们在任何给定时间处理大约30个这样的文件,并且存在一些依赖关系,我们将每个文件存储在Resque队列中,并让多个服务器处理这些(优先级)队列。
简而言之:
class Worker
def self.perform(file)
CsvParser.each(file) do |line|
next unless check_line_with_redis(line)
a = ObjectA.find_or_initialize_by_reference(line[:reference])
a.object_bs.destroy_all
a.update_attributes(line)
end
end
这适用于水平缩放(更多CSV文件=更多服务器),但较大的CSV文件会造成问题。我们目前有这样的文件需要花费超过75小时来解析。我已经想到了许多优化:
一个是减少MySQL查询;我们实例化AR对象,而使用纯SQL的插入,如果我们知道对象Id,则要快得多。通过这种方式,我们可以摆脱大部分AR,甚至Rails也可以通过这种方式消除开销。我们不能使用普通的MySQL加载数据,因为我们必须将CSV记录映射到可能具有不同ID的其他实体(我们将十几个旧数据库组合到一个新数据库中)。
另一个是试图在同一时间做更多事情。有一些IO等待时间,Redis和MySQL的网络等待时间,甚至在MRI使用绿色线程时,这可能允许我们在IO读取等的同时安排我们的MySQL查询。但是使用以下代码:
class Worker
def self.perform(file)
CsvParser.each(file) do |line|
next unless check_line_with_redis(line)
create_or_join_thread(line) do |myLine|
a = ObjectA.find_or_initialize_by_reference(myLine[:reference])
a.object_bs.destroy_all
a.update_attributes(myLine)
end
end
def self.create_or_join_thread(line)
@thread.join if @thread.present?
@thread = Thread.new(line) do |myLine|
yield myLine
end
end
end
这会慢慢减慢过程。当我ps au
时,它以100%CPU开始,但随着时间的推移,它下降到仅2-3%。在那一刻它根本不插入新记录,它似乎挂起。
我有strace
这个过程,起初我看到MySQL查询经过,一段时间后它似乎根本没有执行我的ruby代码。可能是一个死锁(它在解析CSV的 last 行后挂起,但是进程继续以5%的CPU运行并且没有退出),或者我在这里阅读的内容:http://timetobleed.com/ruby-threading-bugfix-small-fix-goes-a-long-way/
我在Ubuntu 10.10上使用Rails 2.3.8,REE,1.8.7-2010.02。任何关于如何处理大量线程的见解(或者为什么不在这里使用线程)都非常感谢!
答案 0 :(得分:1)
这些表上有索引吗?
您可以在批量插入期间暂时禁用这些索引吗?
在我们进行批量插入之前,我们禁用了索引键:
ALTER TABLE foo DISABLE KEYS
完成后,我们启用索引键:
ALTER TABLE foo ENABLE KEYS
来自文档:
ALTER TABLE ... DISABLE KEYS告诉MySQL停止更新非唯一索引。然后应该使用ALTER TABLE ... ENABLE KEYS来重新创建缺失的索引。 MySQL使用一种比逐个插入密钥快得多的特殊算法来实现这一点,因此在执行批量插入操作之前禁用密钥应该会带来相当大的加速。使用ALTER TABLE ... DISABLE KEYS除了前面提到的权限外,还需要INDEX权限。虽然禁用了非唯一索引,但是对于SELECT和EXPLAIN等语句将忽略它们,否则将使用它们。
答案 1 :(得分:1)
你可以尝试在一次交易中包装整个事情 - 这可能会产生很大的不同:
class Worker
def self.perform(file)
ObjectA.transaction do
CsvParser.each(file) do |line|
next unless check_line_with_redis(line)
a = ObjectA.find_or_initialize_by_reference(line[:reference])
a.object_bs.destroy_all
a.update_attributes(line)
end
end
end
end
否则每次保存都将包含在自己的交易中。虽然,对于一个10GB的文件,你可能想要将其分解为每个事务或者其他东西1000个插入。