要对单个 6.20TB 的超大 csv 文件保持顺序的情况下进行去除重复行,有什么好思路?显然不可能加载进内存

2024-06-01 22:14:20 +08:00
 drymonfidelia
15642 次点击
所在节点    程序员
101 条回复
NXzCH8fP20468ML5
2024-06-01 23:05:58 +08:00
1. hash
2. 加序号
3. 按照 hash 分区
4. 逐个处理分区
5. 分区内排序
6. 分区外归并排序

只有单机的话,可以考虑用 duckdb ,多机就用 spark 吧。
yangxin0
2024-06-01 23:07:23 +08:00
分治:
1 、用空间换时间(计算)
2 、用时间(计算)换空间

针对( 1 )有 spark 集群很快的,如果预算有限那么方法( 2 ):
1 、把数据分成 N 块,并针对 N 块内进行去重
2 、从 n 块中取一块,和剩下的 n-1 块去重,取这一块建立 hash or map 都可以,n-1 按照顺序读取
3 、从剩下的 n-1 块中又进行步骤( 2 ), 直到 n=0
4 、经过上述思路处理的 csv 就包含重复
caola
2024-06-01 23:10:33 +08:00
直接存入 kvrocks (硬盘版 redis)
dacapoday
2024-06-01 23:16:41 +08:00
单文件这么大,文件系统压力也不小吧。多数文件系统单文件也不支持这么大吧
james122333
2024-06-01 23:17:30 +08:00
sed 有往下查找一样内容行并删除的工具都可以 其它的都要内存或硬盘空间 vim 就差在它开启文件要暂存 不然也可以
YTMartian
2024-06-01 23:20:19 +08:00
磁盘够用的话,先外部排序,然后直接读取,忽略与上一条相同的数据就行了吧,随机读取文件指定位置,也不用加载进内存
dode
2024-06-01 23:21:30 +08:00
按顺序处理,依据一个合适长度的前缀做分区,逐行文本进行处理,写入到对应分区下面。

检索特定行文本,是否在对应分区内存在,不存在则写入,存在就返回已存在。
chen7897499
2024-06-01 23:27:27 +08:00
emeditor
chen7897499
2024-06-01 23:30:07 +08:00
NotLongNil
2024-06-01 23:33:32 +08:00
上面有人提到的 Bloom Filter 应该是相对最优的解法了,实现简单,占用内存低,速度也快。唯一的问题就是要选择合适的长度,将错误率降低,这需要一定的算法知识,不过现在可以问 AI 了,让 AI 给出公式
ClericPy
2024-06-01 23:37:41 +08:00
有点像 map reduce 场景,归并加快排

问 AI 是好办法
msg7086
2024-06-02 02:57:38 +08:00
我能想到的两种不同的做法。
第一种,在内存不足的情况下,放弃掉内存,直接用 SSD 读写。
在 SSD 上开一个数据库(比如 MySQL 或者 Postgres ),把已经存在的 hash 写到数据库里。
然后流式扫描每一行,取 hash 比对数据库,如果存在 hash 就跳过,不存在就写到结果集里并添加到数据库。
要快速稳妥可以用两种不同的 hash ,比如 xxHash 做一次过滤,SHA1 做二次检验。

第二种,在内存不足的情况下,分批处理。
多次流式扫描每一行,取 hash ,每次只处理 hash 第一个 hex 字符相同的那些数据。
第一次只索引和处理 sha1hash[0] == '0',第二次只索引和处理'1',这样可以把内存需求降到 1/16 ,缺点是 hash 计算也会是 16 倍。
稍微优化一下的话,可以在第一次遍历的时候在数据上追加 sha1hash[0]作为分区标记,这样后面 15 次就不会重复计算,缺点是会每行多一两个字节,而且要多写入一次磁盘。
esee
2024-06-02 03:18:00 +08:00
什么叫保级顺序?比如在一百万的位置和 200 万的位置有重复项,则删除后面重复的那个是么。然后能提供多大的内存和硬盘,
wxf666
2024-06-02 03:52:41 +08:00
想到个方法,预计耗时:10 小时,准确率:100% 剔除重复行。


## 简单流程

1. 分块排序。
2. 同时循环每块,删掉非首次出现的重复行。
3. 分别循环每块,按行号顺序,输出未被删掉的行。


## 详细流程

1. 分块 240GB 文件,每块排序后,写入固态。同时保存每行长度+原始偏移量(约 (240 << 30) / 335 * 8 / 1024 ^ 3 = 5.7 GB )。
2. 利用小根堆,流式排序(按 <string, offset> 排)所有分块每一行。非首次出现行,保存该行偏移量,到相应块的删除名单里。
3. 循环每块,排序原始偏移量、删除名单,按序输出(未被删除的)相应行,至最终文件。


## 耗时计算

1. 顺序读写:9 小时( 3 次顺序读,2 次顺序写,假设都为 1GB/s )。
2. 内存字符串排序:< 1 小时(实测轻薄本 i5-8250U ,每秒归并排序 200W 次 335 长度的随机字符串,约 6900W 次比较)。
- 多线程快排/归并:`(每块行数 = (240 << 30) / 335) * log2(每块行数) * 块数 = 6017 亿` 次比较,我的轻薄本 8 线程需 0.3 小时。
- 单线程小根堆:`202e8 * log2(块数 = 6.2 * 1024 / 240 = 26.5) * 2 = 1910 亿` 次比较,需 0.7 小时。
wxf666
2024-06-02 04:13:29 +08:00
34 楼纠正下数据,实测轻薄本 i5-8250U ,1.5 秒归并排序 320W 个 336 长度的随机字符串,约 6500W 次比较。

- 多线程快排/归并:总计 6017 亿次比较,我的轻薄本 8 线程需 0.5 小时。
- 单线程小根堆:总计 1910 亿次比较,单线程需 1.2 小时。

差不太远。。
wxf666
2024-06-02 05:40:34 +08:00
@dcsuibian #2 ,@opengps #3 ,@msg7086 #32:
如果数据库,每秒写入 10W 条,总计要 203e8 / 1e5 / 3600 = 56 小时?

@YTMartian #26 ,@dode #27:
就算固态 4K 随机读写有 10W IOPS ,算下来也要 56 小时吧?

wxf666
2024-06-02 05:42:19 +08:00
@cndenis #14 ,@hbcolorful #17 ,@NotLongNil #30:

用布隆过滤,几十 GB 好像不够。
在线算了下,50 GB + 15 函数,都会有 1 / 25000 概率出错。。
250 GB + 11 函数,算完 203 亿行,才能有 83.8% 的概率,一个不出错?


@phrack #15:

压缩内存,来存 hash ?好像真的可行。。
平均而言,写入 (372 << 30) / 4096 = 1 亿次,就会占满 372 GB 内存页。即,几乎一开始就会启用 zram ?
我在别处看了看,lz4 每秒能有 200W 次 IO ,算下来要 2.8 小时即可?
话说,这个和 Bloom Filter 相比,哪个出错概率小呢?


dingwen07
2024-06-02 05:56:57 +08:00
@wxf666
Bloom filter 的假阳性率是要看哈希函数的数量的吧
hobochen
2024-06-02 06:20:59 +08:00
大概想了一下,一定深度的前缀树,叶子节点是哈希表或者平衡树存原来的行号应该是一个可行的方案。
hobochen
2024-06-02 06:22:24 +08:00
更正:叶子节点应当是一个哈希表/平衡树; k 是哈希值,v 是行号

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1046023

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX