三种列式存储源代码分析读取差异

三种列式存储源代码分析读取差异

tablename : t
列信息:A,B,C三列,列数据量相同,类型为string
表数据量大小:300G,每列数据100G
主要分析两种类型的sql对于不同存储格式的读取数据量大小

Q1:select count(distinct A) from t;
Q2:select * from t where A='1';

1. PARQUET

1.1 结构图

说明:最小行级别块为:row group

1575256561463.png

1.2 读取parquet代码解析

  1. 读取数据时需要传入schema信息,schema信息包含列信息
ParquetFileReader parquetFileReader = new ParquetFileReader(configuration, parquetFilePath,
                readFooter.getBlocks(), schema.getColumns());
PageReadStore pages = null;
while (null != (pages = parquetFileReader.readNextRowGroup())) {
  1. 通过ParquetFileReader.readNextRowGroup可以看出来,根据schema信息组装列信息(无索引利用情况)
public PageReadStore readNextRowGroup() throws IOException {
        if (this.currentBlock == this.blocks.size()) {
            return null;
        } else {
            BlockMetaData block = (BlockMetaData)this.blocks.get(this.currentBlock);
            if (block.getRowCount() == 0L) {
                throw new RuntimeException("Illegal row group of 0 rows");
            } else {
                ColumnChunkPageReadStore columnChunkPageReadStore = new ColumnChunkPageReadStore(block.getRowCount());
                List<ParquetFileReader.ConsecutiveChunkList> allChunks = new ArrayList();
                ParquetFileReader.ConsecutiveChunkList currentChunks = null;
                Iterator i$ = block.getColumns().iterator();

                while(true) {
                    ColumnChunkMetaData mc;
                    ColumnDescriptor columnDescriptor;
                    do {
                        if (!i$.hasNext()) {
                            i$ = allChunks.iterator();

                            while(i$.hasNext()) {
                                ParquetFileReader.ConsecutiveChunkList consecutiveChunks = (ParquetFileReader.ConsecutiveChunkList)i$.next();
                                List<ParquetFileReader.Chunk> chunks = consecutiveChunks.readAll(this.f);
                                Iterator i$ = chunks.iterator();

                                while(i$.hasNext()) {
                                    ParquetFileReader.Chunk chunk = (ParquetFileReader.Chunk)i$.next();
                                    columnChunkPageReadStore.addColumn(chunk.descriptor.col, chunk.readAllPages());
                                }
                            }

1.3 读取量结果

parquet 数据量大小
Q1 100G
Q2 300G

2. ORCFILE

2.1 结构图

说明:最小行级别块为:stripe


1575256520904.png

2.2 读取orcfile代码解析

1.orcReader

SearchArgument sArg = SearchArgumentFactory
            .newBuilder()
            .equals("x", PredicateLeaf.Type.LONG, 9996l)
            .build();
    String[] sCols = new String[]{"x", null };
VectorizedRowBatch batch = readSchema.createRowBatch();
    Reader.Options options = reader.options().schema(readSchema);
    if (sArg != null && sCols != null) {
      options.searchArgument(sArg, sCols);
    }
    RecordReader rowIterator = reader.rows(options);

2.RecordReaderImpl.java

private void readStripe() throws IOException {
    StripeInformation stripe = beginReadStripe();
    planner.parseStripe(stripe, fileIncluded);
    includedRowGroups = pickRowGroups();

    // move forward to the first unskipped row
    if (includedRowGroups != null) {
      while (rowInStripe < rowCountInStripe &&
          !includedRowGroups[(int) (rowInStripe / rowIndexStride)]) {
        rowInStripe = Math.min(rowCountInStripe, rowInStripe + rowIndexStride);
      }
    }

    // if we haven't skipped the whole stripe, read the data
    if (rowInStripe < rowCountInStripe) {
      planner.readData(indexes, includedRowGroups, false);
      reader.startStripe(planner);
      // if we skipped the first row group, move the pointers forward
      if (rowInStripe != 0) {
        seekToRowEntry(reader, (int) (rowInStripe / rowIndexStride));
      }
    }
  }
  1. pickRowGroup(Pick the row groups that we need to load from the current stripe.return an array with a boolean for each row group or null if all of the row groups must be read)看如下代码,sargApp是过滤条件,后面几个表示当前stripe,还有index信息,通过这几个条件判断当前stripe是否需要读取。
protected boolean[] pickRowGroups() throws IOException {
    // if we don't have a sarg or indexes, we read everything
    if (sargApp == null) {
      return null;
    }
    readRowIndex(currentStripe, fileIncluded, sargApp.sargColumns);
    return sargApp.pickRowGroups(stripes.get(currentStripe),
        indexes.getRowGroupIndex(),
        indexes.getBloomFilterKinds(), stripeFooter.getColumnsList(),
        indexes.getBloomFilterIndex(), false);
  }

构造读取时分两部分,通过跟踪代码发现


1575256250324.png

会先使用index(有三种类型的index过滤) 进行stripe数据剪技,再使用schema进行列值还原。比parquet多一步使用index过滤阶段

2.3 读取量结果

假如通过index(max,min,bloomfilter等)过滤量数据量大小为xG

orcfile 数据量大小
Q1 100G
Q2 300G-X

2.4 hive配置语句

CREATE TABLE lxw1234_orc2 stored AS ORC 
TBLPROPERTIES
('orc.compress'='SNAPPY',
'orc.create.index'='true',
"orc.bloom.filter.columns"="pcid",
'orc.bloom.filter.fpp'='0.05',
'orc.stripe.size'='10485760',
'orc.row.index.stride'='10000') 
AS 
SELECT CAST(siteid AS INT) AS id,
pcid 
FROM lxw1234_text 
DISTRIBUTE BY id sort BY id;

2.4 例子分析(写数据排序)

每个stripe由行组成,每行数据默认10000行

写数据设置
1575268150641.png

读数据设置
1575268169113.png

过滤数据结果呈现
1575268124206.png

使用coreWriter写一个文件,有100000行数据,x的范围是1-100000;然后我构造SearchArgument(x=99961),从过滤结果呈现可以看出来,过滤了前90000行数据,把当前stripe需要读取的rowInStripe设置为90000.

2.5 例子分析2(写数据列值随机)

1575269098965.png

1575269110150.png

1575269070512.png

使用coreWriter写一个文件,有100000行数据,x的范围是1-100000;但不是有序的,是随机的,从过滤结果来看,没有过滤掉数据,因此需要读取全部的stripe的数据。

3. CARBONDATA

3.1 结构图

说明:最小行级别块为:blocklet


1575256853170.png

3.2 读取carbondata代码解析

public BlockletScannedResult scanBlocklet(RawBlockletColumnChunks rawBlockletColumnChunks)
      throws IOException, FilterUnsupportedException {
    if (blockExecutionInfo.isDirectVectorFill()) {
      return executeFilterForPages(rawBlockletColumnChunks);
    } else {
      return executeFilter(rawBlockletColumnChunks);
    }
  }

跟踪了一个select语句,会发现,如果有过滤条件,会到BlockletFilterScanner.scanBlocklet,其中isDirectVectorFill是用来控制是否把结果直接交给spark。跟executeFilter方法会发现方法上注释如下:

   This method will process the data in below order
   1. first apply min max on the filter tree and check whether any of the filter
   is fall on the range of min max, if not then return empty result
   2. If filter falls on min max range then apply filter on actual
   data and get the filtered row index
   3. if row index is empty then return the empty result
   4. if row indexes is not empty then read only those blocks(measure or dimension)
   which was present in the query but not present in the filter, as while applying filter
   some of the blocks where already read and present in chunk holder so not need to
   read those blocks again, this is to avoid reading of same blocks which was already read
   5. Set the blocks and filter indexes to result

总结一下逻辑

  1. page使用最大最小值进行过滤,如果符合,进行实际数据过滤,然后拿到row index;
  2. 如果row index为空,返回空
  3. 如果不为空,拿其他列(这时如果已经读取过的block不需要再次读取)
  4. 返回block与index
    所以carbon是通过过滤列的列值先定位到数据的row index,再通过row index去取其他列的数据。其花费的代价为过滤列的总量-过滤的数据量+取其他列时读取的数据量(这块最小单位为page)

3.3 读取量结果

跟上面一样,假如通过index(max,min,bloomfilter等)过滤量数据量大小为xG,假如需要读取的其他列的Page总数据量为yG

carbondata 数据量大小
Q1 100G
Q2 100G-x+y

4 结论

4.1 对比

item/存储格式 parquet orcfile carbondata
Q1 100G 100G 100G
Q2 300G 300G -x 100G-x+y
索引 column min/max,bloomfilter cloumn min/max,bloomfilter,inverted index(占空间大)
行列存储最小单位 row group stripe blocklet
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351