我有一个用PHP编写的Cronjob脚本,有以下要求:
- 步骤 1(
- 数据库服务器 1):从多个表中获取一些数据(我们这里有很多数据)
- 步骤 2(应用程序服务器):执行一些计算
- 步骤3(数据库服务器2):计算后,将该数据插入另一个数据库(MySQL)/表(InnoDB)以进行报告。此表包含 97 列,实际上是不同的比率,无法进一步规范化。这是不同的物理数据库服务器,只有一个数据库。
脚本在开发过程中运行良好,但在生产中,步骤 1 返回了大约 5000 万条记录。结果,很明显,脚本运行了大约 4 天,然后失败。(粗略估计,按照目前的速度,大约需要 171 天才能完成)
请注意,我们使用预准备语句,步骤 1 是一次获取 1000 条记录中的数据。
我们到目前为止做了什么
优化步骤1:插入和删除所有索引中的多个值
一些测试显示插入(上面的步骤3)花费的时间最长(超过95%的时间)。为了优化,经过一些谷歌搜索,我们从表中删除了所有索引,而不是一个插入查询/行,我们没有一个插入查询/100 行。这给了我们更快的插入速度,但根据粗略估计,运行一次 cron 需要 90 天,我们需要每月运行一次,因为每个月都有新数据可用。
优化步骤2,不是写入数据库,而是写入csv文件,然后使用linux命令导入mysql。
此步骤似乎不起作用。在 CSV 文件中写入 30000 行需要 16 分钟,我们仍然需要在 MySQL 中导入该 CSV 文件。对于所有写入操作,我们只有一个文件处理程序。
当前状态
看来我现在对还能做什么一无所知。一些关键要求:
- 脚本需要插入大约 50,000,000 条记录(会随着时间的推移而增加)
- 每条记录有 97 列,我们可以跳过一些,但至少 85 列。
- 根据输入,我们可以将脚本分解为三个不同的 cron 以在三个不同的服务器上运行,但插入必须在一个数据库服务器(主服务器)上完成,所以不确定它是否有帮助。
然而:
- 我们愿意更改数据库/存储引擎(包括NoSQL)
- 在生产环境中,我们可以有多个数据库服务器,但插入只能在主服务器上完成。所有读取操作都可以定向到从属,这是最小和偶尔的(只是为了生成报告)
问题
我不需要任何描述性的答案,但有人可以简单地提出可能的解决方案吗?我只需要一些优化提示,我会做剩下的研发。
我们对一切开放,更改数据库/存储引擎,服务器优化/多个服务器(数据库和应用程序),更改编程语言或任何满足上述要求的最佳配置。
最终期望,cron 必须在最多 24 小时内完成。
在优化步骤 2 中进行编辑
为了进一步理解为什么生成csv需要时间,我创建了代码的副本,只包含必要的代码。该代码存在于 git https://github.com/kapilsharma/xz
实验输出文件 https://github.com/kapilsharma/xz/blob/master/csv/output500000_batch5000.txt
如果您检查上面的文件,我将插入 500000 条记录并一次从数据库中获取 5000 条记录,使循环运行 100 次。第一个循环的时间为 0.25982284545898 秒,但第 100 个循环的时间为 3.9140808582306。我认为这是因为系统资源和/或csv文件的文件大小。在这种情况下,它更多地成为编程问题,然后是数据库优化。不过,有人可以建议为什么在下一个循环中花费更多时间吗?
如果需要,将提交除csv文件和sql文件外的整个代码,以创建虚拟数据库,因为这些文件非常大。但是,它们可以通过代码轻松生成。
使用 OFFSET
和 LIMIT
遍历表格是 O(N*N),这比您想要或预期的要慢得多。
相反,走过表格"记住你离开的地方"。 最好将PRIMARY KEY
用于此类。 由于 id 看起来像一个没有间隙的AUTO_INCREMENT
,因此代码很简单。 我的博客讨论了这个问题(以及更复杂的分块技术)。
它不会快整整 100 (500K/5K),但会明显更快。
这是一个非常广泛的问题。我首先要弄清楚"插入"语句的瓶颈是什么。运行代码,并使用操作系统为您提供的任何内容来查看计算机正在执行的操作。
如果瓶颈是CPU,则需要找到最慢的部分并加快速度。鉴于您的示例代码,不太可能,但可能。
如果瓶颈是I/O或内存,那么您几乎肯定需要更好的硬件或基本的重新设计。
重新设计这一点的明显方法是找到一种方法来仅处理 50M 记录中的增量。例如,如果您可以在记录更改时写入审计表,则 cron 作业可以查看该审计表并挑选自上次批处理运行以来修改的任何数据。
我在 CakePHP 上有一个邮件 cron 作业,仅在 600 行获取并向注册用户发送电子邮件时失败。它甚至无法在批处理操作中执行作业。我们最终选择了山魈,从那时起一切都很顺利。
我建议(认为在生产中接触遗留系统是一个坏主意):
- 在戈朗或节点中安排一个 mirco 解决方案.js考虑性能基准,因为涉及数据库交互 -这些你都会没事的。让此微解决方案执行cron 作业。(获取 + 计算)
- 来自NoSQL的报告将是具有挑战性,因此您应该尝试使用可用的服务,例如谷歌大查询。将 cron 作业存储数据发送到谷歌大查询,即使在生成报告。
或
- 将每一行插入到原始数据库服务器 1 中后,设置一种消息传递机制,该机制在每次插入时执行 cron 作业的操作(某种触发器),并将其存储到报表服务器中。您可以使用的服务是:Google PubSub或Pusher。我认为每次插入时间消耗会少得多。(还可以使用异步服务设置,该设置执行存储到报表数据库中的任务)。
希望这有帮助。