VectorizedReader和ORC

bymain發表於2018-07-10

spark SQL not only SQL

1.SparkSession/DataFrame/Datasets API
2.Catalyst Optimization & Tungsten Execution
3.DataSource Connectors/  Spark Core(RDD API)

優化儘可能的發生晚些,因為spark SQL,可以通過函式和庫優化
整體的優化使用庫和sql/dataframe

RUN EXPLAIN plan
Interpret  plan
tune plan

https://dbricks.co/2rR8vAr

optimizer:
使用啟發式和代價重寫查詢計劃

        column pruning:列裁剪, outer join elimination:消除outer join
        Predicate push down:謂詞下推, constraint propagation:約束傳播(broadcast)
        constant floding:常量累加: join reordering: join重排序
        .....

    spark.sql.autoBroadcastJoin.threshold
    keep the statistics updated 
    broadcastJoin Hint

memory manager:
    跟蹤記憶體的使用,有效的分配記憶體在task和運算元
    code generator: 編譯物理計劃到優化後的java程式碼

Tungsten Engine: 高效的二進位制資料格式和資料結構對cpu和記憶體的高效。

調整spark.sql.codegen.hugeMethodLimit去避免較大的方法(>8k), 因為這不能被JIT編譯器
所編譯。

spark分為計算和儲存:

完整的資料流:
    外部儲存給spark 喂資料
    spark處理資料
如果spark處理資料很快,資料來源就可能稱為瓶頸。

更高效的去讀取柱狀的向量化資料
更高效的使用jvm生成simd 說明


指定的檔案系統可以完成跳過不必要的資料和預shuffle,可以通過不必要的shuffle和IO來加速查詢

選擇支援向量化讀取的資料來源(parquet,orc)
基於檔案的資料來源,儘可能的建立分割槽,桶。

Spark 2.3.0支援ORC Vectorized向量化原始碼分析

在Spark2.3.0的release文件中,提到ORC Vectored帶來的效能提升:

提高scan吞吐2-5倍;
開啟條件:spark.sql.orc.impl=native;

ORC 檔案型別

當然該ISSUE的提出還是有些背景的(https://issues.apache.org/jira/browse/SPARK-16060),ORC檔案格式本身是Hortonworks提出的針對Hive查詢的一種列式儲存方案,ORC是在一定程度上擴充套件了RCFile,是對RCFile的優化。有別於Facebook的RCFile型別,ORC有如下優點:

  • ORCFile在RCFile基礎上引申出來Stripe和Footer等。每個ORC檔案首先會被橫向切分成多個Stripe,而每個Stripe內部以列儲存,所有的列儲存在一個檔案中,而且每個stripe預設的大小是250MB,相對於RCFile預設的行組大小是4MB,所以比RCFile更高效;
  • ORCFile擴充套件了RCFile的壓縮,除了Run-length(遊程編碼),引入了字典編碼和Bit編碼;
  • ORCFile儲存了檔案更多的元資訊;

其儲存格式如下:

IndexData中儲存了該stripe上資料的位置資訊,總行數等資訊
RowData以stream的形式儲存了資料的具體資訊
Stripe Footer中包含該stripe的統計結果,包括Max,Min,count等資訊

IndexData
RowData
StripeFooter

...

FileFooter中包含該表的統計結果,以及各個Stripe的位置資訊
Postscripts中儲存該表的行數,壓縮引數,壓縮大小,列等資訊
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

ORC Vectored使用場景

spark.sql.orc.impl=native的判斷

其中spark.sql.orc.impl有native和hive兩種選擇,如果針對orc型別選擇hive格式直接呼叫org.apache.spark.sql.hive.orc.OrcFileFormat類實現類的載入,而如果為native則會基於org.apache.spark.sql.execution.datasources.orc.OrcFileFormat類進行載入。

 /** Given a provider name, look up the data source class definition. */
  def lookupDataSource(provider: String, conf: SQLConf): Class[_] = {
    val provider1 = backwardCompatibilityMap.getOrElse(provider, provider) match {
      case name if name.equalsIgnoreCase("orc") &&
          conf.getConf(SQLConf.ORC_IMPLEMENTATION) == "native" =>
        classOf[OrcFileFormat].getCanonicalName
      case name if name.equalsIgnoreCase("orc") &&
          conf.getConf(SQLConf.ORC_IMPLEMENTATION) == "hive" =>
        "org.apache.spark.sql.hive.orc.OrcFileFormat"
      case name => name
    }

    ...
  }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

org.apache.spark.sql.execution.datasources.orc.OrcFileFormat

什麼時候支援ORC Vectored?

  override def supportBatch(sparkSession: SparkSession, schema: StructType): Boolean = {
    val conf = sparkSession.sessionState.conf
    conf.orcVectorizedReaderEnabled && conf.wholeStageEnabled &&
      schema.length <= conf.wholeStageMaxNumFields &&
      schema.forall(_.dataType.isInstanceOf[AtomicType])
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

需要滿足以下條件:
* 開啟spark.sql.orc.enableVectorizedReader: 預設true;
* 開啟spark.sql.codegen.wholeStage: 預設true並且其scheme的長度不大於wholeStageMaxNumFields(預設100列);
* [關鍵]所有列資料型別需要為AtomicType型別的;

AtomicType型別,可根據定義檢視:

/**
 * An internal type used to represent everything that is not null, UDTs, arrays, structs, and maps.
 */
protected[sql] abstract class AtomicType extends DataType {
  private[sql] type InternalType
  private[sql] val tag: TypeTag[InternalType]
  private[sql] val ordering: Ordering[InternalType]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

AtomicType代表了非 null/UDTs/arrays/structs/maps型別。所以如果所含列中如果包含null/UDTs/arrays/structs/maps型別,依然無法收到該ISSUE的便利。

ORC Vectored實現

OrcColumnarBatchReader的使用

在OrcFileFormat.buildReaderWithPartitionValues中:

    if (enableVectorizedReader) {
          val batchReader = new OrcColumnarBatchReader(
            enableOffHeapColumnVector && taskContext.isDefined, copyToSpark)
          // SPARK-23399 Register a task completion listener first to call `close()` in all cases.
          // There is a possibility that `initialize` and `initBatch` hit some errors (like OOM)
          // after opening a file.
          val iter = new RecordReaderIterator(batchReader)
          Option(TaskContext.get()).foreach(_.addTaskCompletionListener(_ => iter.close()))

          // 呼叫initialize函式
          batchReader.initialize(fileSplit, taskAttemptContext)
          // 呼叫initBatch
          batchReader.initBatch(
            reader.getSchema,
            requestedColIds,
            requiredSchema.fields,
            partitionSchema,
            file.partitionValues)
          // 生成iter
          iter.asInstanceOf[Iterator[InternalRow]]
    } else {
          val orcRecordReader = new OrcInputFormat[OrcStruct]
            .createRecordReader(fileSplit, taskAttemptContext)
          val iter = new RecordReaderIterator[OrcStruct](orcRecordReader)
          Option(TaskContext.get()).foreach(_.addTaskCompletionListener(_ => iter.close()))

          val fullSchema = requiredSchema.toAttributes ++ partitionSchema.toAttributes
          val unsafeProjection = GenerateUnsafeProjection.generate(fullSchema, fullSchema)
          val deserializer = new OrcDeserializer(dataSchema, requiredSchema, requestedColIds)

          if (partitionSchema.length == 0) {
            iter.map(value => unsafeProjection(deserializer.deserialize(value)))
          } else {
            val joinedRow = new JoinedRow()
            iter.map(value =>
              unsafeProjection(joinedRow(deserializer.deserialize(value), file.partitionValues)))
          }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

OrcColumnarBatchReader的實現

initialize(): 初始化OrcFile Reader及Hadoop環境配置;
initBatch(): 初始化batch變數和columnarBatch變數(其中batch為ORC Reader向量化每次讀取的結果儲存變數,columnarBatch為codegen轉換為Spark定義型別儲存變數 );
nextBatch(): 迭代器,其核心還是呼叫ORC自定義的vectored函式,需要根據型別轉換Spark定義type;

單元測試

參考: org.apache.spark.sql.hive.orc.OrcReadBenchmark

結果分析:
針對數字、String型別測試

Native ORC Vectorized > Native ORC Vectorized with copy > Native ORC MR > Hive built-in ORC

針對分割槽、不分割槽測試

Partition效能遠遠>不分割槽效能

參考:


相關文章