网站首页 > 知识剖析 正文
最近在SSENSE,负责构建推荐引擎一部分的我的团队"大砍刀"处理了一项我们必须准备并使用相对较大的数据集的任务。 数据还不够大,无法保证使用Spark或Hadoop MapReduce之类的东西,但它又大到足以迫使我们考虑分别存储和读取此数据的时空复杂性。 简而言之,我们需要一种可以合理压缩并提供快速读取时间的文件格式。 在研究此问题的解决方案时,我有机会探索了一些用于存储数据的常用文件类型的内部工作原理。 在本文中,我将分享我的笔记和从该过程中学到的东西。
竞争者
测试的3种文件类型是:
· CSV
· Parquet
· Avro
CSV
为了测试CSV,我生成了一个伪造的目录,其中包含约70,000种产品,每种产品都有特定的分数和任意字段,只是向文件添加了一些额外的字段。
鉴于我们的要求极低,因此文件仅包含时间戳,产品ID和产品评分。
示例输出为:
spring_exclusives.csv
timestamp,product_id,product_score
1555480889,46260,0.3381890166444166
1555480889,85461,0.34786700678437343
1555480889,85462,0.2932997952484385
1555480889,113723,0.3803736086217753
1555480889,113724,0.39684375226672763
1555480889,113727,0.38768682121073605
最后结果
一个大约有70,000行的文件,大小为1.3MB。 使用Node.js内置的fs createReadStream,整个文件解析和处理每一行大约需要122毫秒。
十分简单!
更大的数据集
CSV适用于70,000行的小型数据集。 但是,如果您需要增加到200万条记录怎么办? 如果每个记录都具有嵌套属性怎么办? 尽管CSV文件简单易读,但不幸的是它们无法很好地缩放。 随着文件大小的增加,加载时间变得不切实际,并且无法优化读取。 如上所示,它们的原始性质使它们成为较小数据集的理想选择,但对管理较大数据集非常不便。 这是Parquet和Avro都加入的地方。
以下示例假设一个假设的场景,该场景尝试存储成员及其品牌色彩偏好是什么。 例如:Sarah的ID为10,她非常喜欢红色的Nike(ID 1),蓝色的Adidas(ID 2)和绿色的Reebok(ID 3)。
Parquet
Apache Parquet定义为:
"无论数据处理框架,数据模型或编程语言的选择如何,Hadoop生态系统中任何项目均可使用的列式存储格式"
列存储与基于行的存储从根本上不同。 尽管基于行的存储已针对读取一系列行进行了优化,但对列式存储进行了优化以选择一部分列。 让我们看一些代码。 可以在Parquet的网站上找到详细的文档。
使用Parquet,我创建了以下形状的嵌套模式:
var schema = new parquet.ParquetSchema({ memberID: { type: 'UINT_32'}, brand_color: { repeated: true, fields: { brand: { type: 'UINT_32'}, color: { type: 'UTF8' }, score: { type: 'FLOAT'} } }, timestamp: { type: 'TIMESTAMP_MILLIS' } });
这样,我生成了一个文件,该文件包含2,000,000个成员,每个成员具有10个品牌颜色首选项。 总文件大小为335 MB。
为了稍微减小文件大小,我应用了快速的编解码器压缩。 这样做是微不足道的,因为这只是对模式的一小部分更改。
var schema = new parquet.ParquetSchema({ memberID: { type: 'UINT_32'}, brand_color: { repeated: true, fields: { brand: { type: 'UINT_32', compression: 'SNAPPY'}, color: { type: 'UTF8', compression: 'SNAPPY' }, score: { type: 'FLOAT', compression: 'SNAPPY'} } }, timestamp: { type: 'TIMESTAMP_MILLIS' }, });
文件大小从335 MB减少到69.7 MB。
我的脚本花了大约50秒钟才能浏览整个文件,仅需简单阅读即可。 如前所述,Parquet的主要优点是它使用了列式存储系统,这意味着如果只需要每个记录的一部分,则读取的延迟会大大降低。 这是10次读取的结果:
通过只拔出memberID列而不是整个记录,读取时间从50秒变为仅1秒!
这是只提取memberID的读取函数:
async function testParquetRead(){
let reader = await parquet.ParquetReader.openFile('test.parquet');
let cursor = reader.getCursor(['memberID']);
let record = null;
let i = 0;
let lastone;
while(record = await cursor.next()) {
lastone = record; i++;
}
await reader.close();
console.log(lastone);
console.log(i);
}
我决定运行10次,以便对结果有一定的意义,并确保1秒不仅仅是一次。
但是,就我们而言,我们一直都需要整个记录,因此这并不是什么优势。
Avro
Avro的一大优势是架构,它比Parquet的架构要丰富得多。 它使我们能够通过添加,删除或修改记录的列来发展模式,这比Parquet更加容易。
根据Avro自己的网站:
" Avro依赖于架构。 读取Avro数据时,将始终存在写入数据时使用的架构。 这样就可以在没有每个值开销的情况下写入每个数据,从而使序列化既快又小。 这也方便了动态脚本语言的使用,因为数据及其模式完全是自描述的"
此外,Avro的存储基于行而不是列。 这使得它更适合读取整个行系列。
使用Avro,我创建了一个具有以下形状的非常相似的嵌套模式:
var schema = new avro.Type.forSchema({ type: 'record', fields: [ {name: 'memberID', type:'int'}, { name: 'brand_color', type: { type: 'array', items: { "type": "record", "name":"brandColor", "fields":[ {name: 'brand', type: 'int'}, {name: 'color', type: 'string'}, {name: 'score', type: 'float'} ] }, } }, {name: 'timestamp', type:'long'} ] });
使用此架构,然后生成了一个文件,其中包含2,000,000个具有10个品牌颜色首选项的成员。 文件总大小为181 MB,完全没有压缩,仅为Parquet文件的一半!
尽管这看起来很有希望,但是为了测试它的缩放比例,我决定将品牌颜色偏好增加10倍,对于2,000,000个成员,最多增加100倍,以查看此文件的大小。 数据的增加也密切反映了我们的实际需求。 文件总大小增加到1.5GB。 虽然内存占用量的增加几乎与数据大小的增加成正比,但对于快速访问和读取而言,它太大了。
下一步是进行一些压缩,看看可以将文件缩小多少。 我决定使用与Parquet测试中使用的相同的快速代码进行尝试。 应用代码是微不足道的,所有必要的就是从以下位置更改写入:
var writer = avro.createFileEncoder('test.avro', schema);
至
var writer = avro.createFileEncoder('test.avro', schema, {
codec: 'snappy', codecs: {
snappy: snappy.compress
}
});
结果:
文件大小为93MB! 先前大小的一小部分。 仅使用10个品牌颜色偏好,文件大小就减少到只有16.3 MB。
以下JavaScript代码遍历整个文件,将每一行变成一个JSON对象,并对操作进行基准测试。
function testAvroRead(schema){
let i =0;
console.time('read')
let lastone;
avro.createFileDecoder('test.avro', {
codecs: {
"snappy": snappy.uncompress
}
})
.on('metadata', (data)=> { console.log(data); })
.on('data', (row) => { lastone = schema.toString(row); i++; })
.on('end', () => { console.log(i); console.log(lastone) console.timeEnd('read')
});
}
请注意,传递到testAvroRead方法的架构与上面定义的架构相同。 这保证了模式验证,该验证必须使用Parquet明确执行。 整个文件的读取,解压缩和验证花费了不到3分钟的时间。 运行读取速度测试10次以确保结果显着,并且输出如下:
虽然我们在读取速度上有些损失,但我们在存储上获得了更多的东西,并且免费获得了验证!
结论
对于我们的特定案例,赢家显然是Avro。 但是请注意,这不会以任何方式抹杀Parquet。 SSENSE的数据湖在很大程度上依赖于Parquet,考虑到我们处理不可变的数据和分析查询(列式存储是最佳的),这很有意义。
所有这些概念证明都是使用Node.js和JavaScript生成的。 这使我们能够以健壮和可扩展的方式压缩许多数据。 内置的架构会检查每条记录,如果什么时候与定义不匹配,则会引发错误,从而使验证变得轻而易举。
(本文翻译自Mikhail Levkovsky的文章《CSV vs Parquet vs Avro: Choosing the Right Tool for the Right Job》,参考:https://medium.com/ssense-tech/csv-vs-parquet-vs-avro-choosing-the-right-tool-for-the-right-job-79c9f56914a8)
猜你喜欢
- 2024-11-17 JS基础进阶- 同步异步编程和EventLoop底层机制
- 2024-11-17 5个你应该知道的JavaScript技巧,不能错过!
- 2024-11-17 map映射+异步加载 完美过渡 if else
- 2024-11-17 Chrome控制台的一些有用API(chrome控件)
- 2024-11-17 Javascript调试器自编代码及运用(js调试工具和方法如何使用)
- 2024-11-17 NET中的定时器:种类与应用场景(winform定时器)
- 2024-11-17 全栈之路:从一个深拷贝开始循序渐进
- 2024-11-17 localStorage灵魂五问。 5M??10M(灵魂官方网站)
- 2024-11-17 JS中用于跟踪程序执行时间的专用函数,两个同时出现截断时间戳
- 2024-11-17 动手写一个简易的 Virtual DOM,加强阅读源码的能力
- 最近发表
- 标签列表
-
- xml (46)
- css animation (57)
- array_slice (60)
- htmlspecialchars (54)
- position: absolute (54)
- datediff函数 (47)
- array_pop (49)
- jsmap (52)
- toggleclass (43)
- console.time (63)
- .sql (41)
- ahref (40)
- js json.parse (59)
- html复选框 (60)
- css 透明 (44)
- css 颜色 (47)
- php replace (41)
- css nth-child (48)
- min-height (40)
- xml schema (44)
- css 最后一个元素 (46)
- location.origin (44)
- table border (49)
- html tr (40)
- video controls (49)