当前位置:首页 > 技术分析 > 正文内容

1分钟插入10亿行数据!抛弃Python,写脚本请使用Rust

ruisui883个月前 (02-03)技术分析15

来源:Avinash

编辑:好困

【新智元导读】近日,一位程序员急需在一分钟之内生成十亿行的测试数据库,然而在用Python写了脚本之后发现「大失败」。怎么办?当然是用Rust了!


最近,一位程序员表示自己急需一个「也就」十亿行数据的测试数据库,并且还得在一分钟之内生成。


于是,他做了一个所有程序员都会做的事:写一个Python脚本来生成数据库。


然而,很不幸的是,这个脚本非 常 慢



于是,他又做了一个所有程序员都会做的事:进一步学习关于SQLite、Python以及不知道为什么还有Rust的知识。


项目已开源:https://github.com/avinassh/fast-sqlite3-inserts


目标

作者需要在他2019年的MacBook Pro(2.4GHz四核i5)上,一分钟内生成一个有10亿行的SQLite数据库


表的模式


要求:


  1. 生成的数据是随机的;
  2. 「area」列将包含六位数的地区代码(任何六位数都可以,不需要验证);
  3. 「age」列是5、10或15中的任何一个;
  4. 「active」列是0或1。


不过,作者表示,对脚本的要求也不用太高,还是可以妥协的:


  1. 如果进程崩溃,所有的数据都丢失也没有问题,再次运行脚本就可以了;
  2. 允许充分利用电脑的资源:100%的CPU,8GB的内存和剩余的SSD储存;
  3. 不需要使用真正的随机方法,来自stdlib的伪随机方法就可以。


Python原型

在最开始的脚本中,作者试图在一个for循环中逐一插入1000万条记录,而这让用时直接达到了15分钟



显然,这太慢了。


在SQLite中,每次插入都是一个事务,每个事务都保证它被写入磁盘,作者推断可能问题就来自这里。


于是作者开始尝试不同规模的批量插入,发现10万是一个最佳点,而运行时间减少到了10分钟



SQLite优化

作者认为自己写的代码已经很简练了,并没有什么可以优化的空间。


于是他将下一个目标转到了数据库的优化。


根据各种关于SQLite优化的建议,作者做了一些改进。



  • 关闭「journal_mode」将禁用回滚日志,也就是说,如果任何事务失败,都无法回滚。


  • 关闭「synchronous」,将使SQLite不再关心是否能可靠地写入磁盘,而是把这个责任交给操作系统。也就是说,可能会出现SQLite并没有成功写入磁盘的情况。


  • 「cache_size」指定了SQLite在内存中可以保留多少个内存页。


  • 当「locking_mode」为「EXCLUSIVE」模式时,SQLite锁住的连接将永远不会被释放。


  • 将「temp_store」设置为「MEMORY」可以让其表现像一个内存数据库。


此处作者提醒,请不要把这些操作用到生产上去。


重新审视Python

作者再次重写了Python脚本,这次包括了微调的SQLite参数,这次带来了巨大的提升,运行时间大幅减少:


  • 原始的for循环版本用时大约10分钟
  • 批处理版本用时大约8.5分钟


PyPy

PyPy在其主页上强调它比CPython快4倍,于是作者决定尝试一下。


令作者有些意外的是,竟然不需要对现有的代码进行任何改动,只需要在PyPy运行就可以了。


批处理版本只需要2.5分钟,也就是速度快了接近3.5倍


Busy Loop?

莫非是在Python的循环上耗费了太多时间?于是作者删除了SQL指令之后再次跑了一遍代码:


  • 批处理版本在CPython中用时5.5分钟


  • 批处理版本在PyPy中用时1.5分钟(又是3.5倍的速度提升)。


然而用Rust重写了相同的内容之后,循环只需要17秒


于是,作者果断抛弃Python转投Rust的怀抱。



Rust

像Python一样,作者先写了一个原始的Rust版本,一个循环执行一行数据的插入。


然而,即便使用了所有SQLite的优化,也依然消耗了大约3分钟。于是作者进行了进一步的测试:


  • 尝试把「rusqlite」换成异步运行的「sqlx」,这让用时直接被拉到了14分钟。作者表示,这比自己迄今为止写的任何一个Python迭代都要差。
  • 在执行原始SQL语句时,使用准备好的语句。这个版本的用时只有1分钟


最优的版本

使用准备好的语句,以50行为一个批次插入,最终用时34.3秒


作者又写了一个线程版本,其中一个线程从通道接收数据,还有四个线程向通道推送数据。


这个也是目前性能最好的版本,最终用时大约32.37秒




IO时间

SQLite论坛上的网友提出了一个有趣的想法:测量内存数据库所需的时间。


于是作者又跑了一遍代码,将数据库的位置设定为「:memory:」,rust版本完成的时间少了两秒(29秒)。


也就是说将1亿条记录写入到磁盘上需要2秒,这个用时似乎也是合理的。


这也说明,可能没有更多的SQLite优化可以以更快的方式写入磁盘,因为99%的时间都花在生成和添加数据上。


排行榜


插入1亿行数据的用时:
Rust33秒 PyPy126秒 CPython210秒


总结


  1. 尽可能使用SQLite PRAGMA语句


  1. 使用准备好的语句


  1. 进行分批插入


  1. PyPy确实比CPython快4倍


  1. 异步不一定更快


目前,第二快的版本是单线程运行的,而作者的电脑有4个核心,于是他在一分钟内可以得到8亿行数据。然后再经过几秒钟的数据合并,时间仍然可以少于一分钟。


网友评论


博主的这一番研究获得了网友们的一致好评。


真的很喜欢这些观点:

学习了更多关于PRAGMA语句。

PyPy的效率和灵活性可以通过即插即用的方式体现(将来一定会给它一个机会)。

文章的排版非常简单,有适当的源代码链接。很有趣,很容易上手。


Rust高光时刻又来了!



参考资料:

https://avi.im/blag/2021/fast-sqlite-inserts/

https://github.com/avinassh/fast-sqlite3-inserts

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/1204.html

标签: pypy3
分享给朋友:

“1分钟插入10亿行数据!抛弃Python,写脚本请使用Rust” 的相关文章

基于Ubuntu的Linux Lite 6.0发行版正式发布

开发者 Jerry Bezencon 今天宣布,基于 Ubuntu 的 Linux Lite 6.0 发行版正式发布。本次更新代号为“Fluorite”,基于 Ubuntu 22.04 LTS (Jammy Jellyfish) 系统,它由长期支持的 Linux 5.15 LTS 内核系列驱动。下载...

红帽最新的企业 Linux 发行版具有解决混合云复杂性的新功能

据zdnet网5月1日报道,红帽这家 Linux 和超云领导者今天发布了其最新的旗舰 Linux 发行版 Red Hat Enterprise Linux (RHEL) 9.4,此前上周宣布对已有十年历史的流行 RHEL 7.9 再支持四年。这个领先的企业 Linux 发行版的最新版本引入了许多新功...

vue中组件之间的通信方式

** 1.1 父子组件**a. 父向子传数据: 第1种: 父通过属性传值,子组件通过props接收数据(注:props传过来的数据是单向的,不可以进行修改)第2种:子组件可以通过$parent来获取父组件里的数据和调用父组件的方法(注:数据是双向的,还要注意如用了UI组件并且在该UI组件里重新定义一...

gitlab简单搭建与应用

一、gitlab1、简介GitLab是利用Ruby on Rails一个开源的版本管理系统,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目。与Github类似,GitLab能够浏览源代码,管理缺陷和注释。可以管理团队对仓库的访问,它非常易于浏览提交过的版本并提供一个文件历...

面试被逼疯:聊聊Python Import System?

面试官一个小时逼疯面试者:聊聊Python Import System?对于每一位Python开发者来说,import这个关键字是再熟悉不过了,无论是我们引用官方库还是三方库,都可以通过import xxx的形式来导入。可能很多人认为这只是Python的一个最基础的常识之一,似乎没有可以扩展的点了,...

多项修正 尼康D4s发布最新1.10版固件

尼康公司与2014年8月27日发布了D4s的最新固件,固件版本号为C:1.10。这次固件升级,主要解决了一些BUG,并且对拍摄菜单与相机操作做了一定调整。下面是本次新固件的具体信息:尼康发布D4s最新C固件 1.10版对C固件升级到1.10版所作的修改:当选定运动VR模式并换上 AF-S 尼克尔 4...