Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
45f4827
support orc file meta cache
LuciferYang Aug 16, 2021
a74c793
remove ForTailCacheReader
LuciferYang Aug 16, 2021
1a9bd3b
rename test case
LuciferYang Aug 16, 2021
30df269
remove private[sql] and add comments
LuciferYang Aug 16, 2021
42d2bfd
Reduce method encapsulation
LuciferYang Aug 16, 2021
0e6c52b
use PrivateMethodTester
LuciferYang Aug 16, 2021
95bae3c
add a configable maximumSize
LuciferYang Aug 16, 2021
ebb7e0b
rename config
LuciferYang Aug 17, 2021
c36a569
move test
LuciferYang Aug 17, 2021
b02de85
update benchmark result
LuciferYang Aug 17, 2021
406f91c
revert config name
LuciferYang Aug 18, 2021
3b15a82
add compile same type
LuciferYang Aug 18, 2021
2b99983
update mirco bench
LuciferYang Aug 18, 2021
82ddf4f
change the default value of ttlSinceLastAccess
LuciferYang Aug 18, 2021
1dd174e
change the default value of ttlSinceLastAccess
LuciferYang Aug 18, 2021
a339b1b
update conf doc to add Warning
LuciferYang Aug 18, 2021
7153d2a
use a list config
LuciferYang Aug 18, 2021
c3838e6
Add checkValue to spark.sql.fileMetaCache.enabledSourceList and test …
LuciferYang Aug 19, 2021
59d5bb9
change to use guava cache and update benchmark
LuciferYang Aug 19, 2021
4adeb62
rename test case
LuciferYang Aug 19, 2021
e5f9497
add SEC to ttl
LuciferYang Aug 19, 2021
ec8fa1c
Revert "change to use guava cache and update benchmark"
LuciferYang Aug 19, 2021
db90daf
Revert "Revert "change to use guava cache and update benchmark""
LuciferYang Aug 22, 2021
7327fdb
Merge branch 'upmaster' into SPARK-36516
LuciferYang Aug 22, 2021
2907b2c
Merge branch 'master' of github.com:apache/spark into SPARK-36516
LuciferYang Aug 23, 2021
a7eff43
Merge branch 'upmaster' into SPARK-36516
LuciferYang Sep 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,28 @@ object SQLConf {
.booleanConf
.createWithDefault(false)

val FILE_META_CACHE_ORC_ENABLED = buildConf("spark.sql.fileMetaCache.orc.enabled")
.doc("To indicate if enable orc file meta cache, it is recommended to enabled " +
"this config when multiple queries are performed on the same dataset, default is false.")
.version("3.3.0")
.booleanConf
.createWithDefault(false)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we take the approach to have a list config for all data sources enabled with file meta cache, instead of one config per data source?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for @viirya 's suggestion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@LuciferYang LuciferYang Aug 17, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ebb7e0b rename this config entry


val FILE_META_CACHE_TTL_SINCE_LAST_ACCESS =
buildConf("spark.sql.fileMetaCache.ttlSinceLastAccess")
.version("3.3.0")
.doc("Time-to-live for file metadata cache entry after last access, the unit is seconds.")
.timeConf(TimeUnit.SECONDS)
.createWithDefault(3600L)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we reduce this from 1 hour = 3600 to 10 minute = 600?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

82ddf4f reduce this from 3600L to 600L


val FILE_META_CACHE_MAXIMUM_SIZE =
buildConf("spark.sql.fileMetaCache.maximumSize")
.version("3.3.0")
.doc("Maximum number of file meta entries the file meta cache contains.")
.intConf
.checkValue(_ > 0, "The value of fileMetaCache maximumSize must be positive")
.createWithDefault(1000)

val HIVE_VERIFY_PARTITION_PATH = buildConf("spark.sql.hive.verifyPartitionPath")
.doc("When true, check all the partition paths under the table\'s root directory " +
"when reading data stored in HDFS. This configuration will be deprecated in the future " +
Expand Down Expand Up @@ -3600,6 +3622,8 @@ class SQLConf extends Serializable with Logging {

def parquetVectorizedReaderBatchSize: Int = getConf(PARQUET_VECTORIZED_READER_BATCH_SIZE)

def fileMetaCacheOrcEnabled: Boolean = getConf(FILE_META_CACHE_ORC_ENABLED)

def columnBatchSize: Int = getConf(COLUMN_BATCH_SIZE)

def cacheVectorizedReaderEnabled: Boolean = getConf(CACHE_VECTORIZED_READER_ENABLED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.orc.OrcFile;
import org.apache.orc.Reader;
import org.apache.orc.TypeDescription;
import org.apache.orc.impl.OrcTail;
import org.apache.orc.mapred.OrcInputFormat;

import org.apache.spark.sql.catalyst.InternalRow;
Expand Down Expand Up @@ -74,6 +75,8 @@ public class OrcColumnarBatchReader extends RecordReader<Void, ColumnarBatch> {
// The wrapped ORC column vectors.
private org.apache.spark.sql.vectorized.ColumnVector[] orcVectorWrappers;

private OrcTail cachedTail;

public OrcColumnarBatchReader(int capacity) {
this.capacity = capacity;
}
Expand Down Expand Up @@ -124,7 +127,8 @@ public void initialize(
fileSplit.getPath(),
OrcFile.readerOptions(conf)
.maxLength(OrcConf.MAX_FILE_LENGTH.getLong(conf))
.filesystem(fileSplit.getPath().getFileSystem(conf)));
.filesystem(fileSplit.getPath().getFileSystem(conf))
.orcTail(cachedTail));
Reader.Options options =
OrcInputFormat.buildOptions(conf, reader, fileSplit.getStart(), fileSplit.getLength());
recordReader = reader.rows(options);
Expand Down Expand Up @@ -189,6 +193,10 @@ public void initBatch(
columnarBatch = new ColumnarBatch(orcVectorWrappers);
}

public void setCachedTail(OrcTail cachedTail) {
this.cachedTail = cachedTail;
}

/**
* Return true if there exists more data in the next batch. If exists, prepare the next batch
* by copying from ORC VectorizedRowBatch columns to Spark ColumnarBatch columns.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.spark.sql.execution.datasources

import java.util.concurrent.TimeUnit

import com.github.benmanes.caffeine.cache.{CacheLoader, Caffeine}
import com.github.benmanes.caffeine.cache.stats.CacheStats
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.Path

import org.apache.spark.SparkEnv
import org.apache.spark.internal.Logging
import org.apache.spark.sql.internal.SQLConf

/**
* A singleton Cache Manager to caching file meta. We cache these file metas in order to speed up
* iterated queries over the same dataset. Otherwise, each query would have to hit remote storage
* in order to fetch file meta before read files.
Comment on lines +31 to +33

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For same dataset, cannot current caching (persisting) mechanism satisfy the requirement? Could you describe the difference between two and what the benefit of file meta?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@viirya . caching (persisting) consumes much resources in memory or disk. We cannot use it at the small executors.
This PR focuses the metadata only as described like in order to fetch file meta before read file.

*
* We should implement the corresponding `FileMetaKey` for a specific file format, for example
* `ParquetFileMetaKey` or `OrcFileMetaKey`. By default, the file path is used as the identification

@dongjoon-hyun dongjoon-hyun Aug 16, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For ORC, could you override this in order to consider getFileModificationTime in addition to path?
I'm taking my word back. Please ignore this.

* of the `FileMetaKey` and the `getFileMeta` method of `FileMetaKey` is used to return the file
* meta of the corresponding file format.
*/
object FileMetaCacheManager extends Logging {

private lazy val cacheLoader = new CacheLoader[FileMetaKey, FileMeta]() {

@LuciferYang LuciferYang Aug 19, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To use Guava Cache or Caffeine, we need to wait conclusion of #33784, now it's keep useCaffeine

override def load(fileMetaKey: FileMetaKey): FileMeta = {
logDebug(s"Loading Data File Meta ${fileMetaKey.path}")
fileMetaKey.getFileMeta
}
}

private lazy val ttlTime =
SparkEnv.get.conf.get(SQLConf.FILE_META_CACHE_TTL_SINCE_LAST_ACCESS)

private lazy val maximumSize =
SparkEnv.get.conf.get(SQLConf.FILE_META_CACHE_MAXIMUM_SIZE)

private lazy val cache = Caffeine
.newBuilder()
.expireAfterAccess(ttlTime, TimeUnit.SECONDS)
Comment thread
dongjoon-hyun marked this conversation as resolved.
.maximumSize(maximumSize)
.recordStats()
.build[FileMetaKey, FileMeta](cacheLoader)

/**
* Returns the `FileMeta` associated with the `FileMetaKey` in the `FileMetaCacheManager`,
* obtaining that the `FileMeta` from `cacheLoader.load(FileMetaKey)` if necessary.
*/
def get(fileMeteKey: FileMetaKey): FileMeta = cache.get(fileMeteKey)

/**
* Return current snapshot of FileMeta Cache's cumulative statistics
* include cache hitCount, missCount and so on.
* This method is only called when testing now.
*/
private def cacheStats: CacheStats = cache.stats()
Comment thread
dongjoon-hyun marked this conversation as resolved.

/**
* Use to cleanUp entries in the FileMeta Cache.
* This method is only called when testing now.
*/
private def cleanUp(): Unit = cache.cleanUp()
}

abstract class FileMetaKey {
def path: Path
def configuration: Configuration
def getFileMeta: FileMeta
override def hashCode(): Int = path.hashCode
override def equals(other: Any): Boolean = other match {
case df: FileMetaKey => path.equals(df.path)
case _ => false
Comment on lines +88 to +90

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, if there will be different types of FileMetaKey later, e.g. ParquetFileMetaKey or OrcFileMetaKey, they should be considered different.

So besides comparing path, should we also check if they are same type?

@LuciferYang LuciferYang Aug 17, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add key.getClass.equals(other.getClass) or other comparison conditions? This is a full path, such as hdfs://nn/path0/path1/xxx.orc, do we need to re-check its type under the same path?

@viirya viirya Aug 17, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then seems we only need a common FileMetaKey class instead of individual one per datasource, if we only care about path?

@LuciferYang LuciferYang Aug 18, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current design is still needed. We need to be able to construct a specific FileMeta(like OrcFileMeta or ParquetFileMeta) through FileMetaKey (path and configuration), and specific FileMetaKey used to determine whether to read the footer of Parquet or the tail of ORC.

If a unified FileMetaKey is used, it seems that a fileType field needs to be added to FileMetaKey to read the corresponding FileMeta.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any suggestions ? @dongjoon-hyun

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@viirya 3b15a82 add same type check, I think you're right

}
}

trait FileMeta
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.catalyst.expressions.codegen.GenerateUnsafeProjection
import org.apache.spark.sql.execution.datasources._
import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.sources._
import org.apache.spark.sql.types._
import org.apache.spark.util.{SerializableConfiguration, Utils}
Expand Down Expand Up @@ -154,11 +155,18 @@ class OrcFileFormat

(file: PartitionedFile) => {
val conf = broadcastedConf.value.value
val metaCacheEnabled =
conf.getBoolean(SQLConf.FILE_META_CACHE_ORC_ENABLED.key, false)

val filePath = new Path(new URI(file.filePath))

val fs = filePath.getFileSystem(conf)
val readerOptions = OrcFile.readerOptions(conf).filesystem(fs)
val readerOptions = if (metaCacheEnabled) {
val tail = OrcFileMeta.readTailFromCache(filePath, conf)
OrcFile.readerOptions(conf).filesystem(fs).orcTail(tail)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happen if the file is replaced?

@LuciferYang LuciferYang Aug 17, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very good question!!! If we want to handle it well, we need a relatively more complex design, such as adding a command to trigger the relevant cleaning of all executors when the file changes.

However, if the file changes are not perceived by spark, I think wrong data will be read here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the file name has the timestamp, I think we don't have to worry too much. The names of the new file and the old file are different and they can ensure that they don't read the wrong data.

@LuciferYang LuciferYang Aug 17, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is manually file replaced and the file has the same name and the corresponding file meta exists in the cache, an incorrect file meta will be used to read the data. If the data reading fails, the job will fail. But if the data reading happens to be successful, the job will read the wrong data.

@LuciferYang LuciferYang Aug 17, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, even if there is no FileMetaCache, there is a similar risk in manually replace files with same name, because the offset and length of PartitionedFile maybe don't match after manually replace for a running job

} else {
OrcFile.readerOptions(conf).filesystem(fs)
}
val resultedColPruneInfo =
Utils.tryWithResource(OrcFile.createReader(filePath, readerOptions)) { reader =>
OrcUtils.requestedColumnIds(
Expand Down Expand Up @@ -200,6 +208,10 @@ class OrcFileFormat
val requestedDataColIds = requestedColIds ++ Array.fill(partitionSchema.length)(-1)
val requestedPartitionColIds =
Array.fill(requiredSchema.length)(-1) ++ Range(0, partitionSchema.length)
if (metaCacheEnabled) {
val tail = OrcFileMeta.readTailFromCache(filePath, conf)
batchReader.setCachedTail(tail)
}
batchReader.initialize(fileSplit, taskAttemptContext)
batchReader.initBatch(
TypeDescription.fromString(resultSchemaString),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.Path
import org.apache.orc.OrcFile
import org.apache.orc.impl.{OrcTail, ReaderImpl}

import org.apache.spark.sql.execution.datasources.{FileMeta, FileMetaCacheManager, FileMetaKey}
import org.apache.spark.util.Utils

case class OrcFileMetaKey(path: Path, configuration: Configuration)
extends FileMetaKey {
override def getFileMeta: OrcFileMeta = OrcFileMeta(path, configuration)
}

case class OrcFileMeta(tail: OrcTail) extends FileMeta

object OrcFileMeta {
def apply(path: Path, conf: Configuration): OrcFileMeta = {
val fs = path.getFileSystem(conf)
val readerOptions = OrcFile.readerOptions(conf).filesystem(fs)
Utils.tryWithResource(new ReaderImpl(path, readerOptions)) { fileReader =>
new OrcFileMeta(new OrcTail(fileReader.getFileTail, fileReader.getSerializedFileFooter))
Comment thread
dongjoon-hyun marked this conversation as resolved.
}
}

def readTailFromCache(path: Path, conf: Configuration): OrcTail =
FileMetaCacheManager.get(OrcFileMetaKey(path, conf)).asInstanceOf[OrcFileMeta].tail
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import org.apache.spark.broadcast.Broadcast
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.connector.read.{InputPartition, PartitionReader}
import org.apache.spark.sql.execution.datasources.PartitionedFile
import org.apache.spark.sql.execution.datasources.orc.{OrcColumnarBatchReader, OrcDeserializer, OrcFilters, OrcUtils}
import org.apache.spark.sql.execution.datasources.orc.{OrcColumnarBatchReader, OrcDeserializer, OrcFileMeta, OrcFilters, OrcUtils}
import org.apache.spark.sql.execution.datasources.v2._
import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.sources.Filter
Expand Down Expand Up @@ -60,6 +60,7 @@ case class OrcPartitionReaderFactory(
private val capacity = sqlConf.orcVectorizedReaderBatchSize
private val orcFilterPushDown = sqlConf.orcFilterPushDown
private val ignoreCorruptFiles = sqlConf.ignoreCorruptFiles
private val orcMetaCacheEnabled = sqlConf.fileMetaCacheOrcEnabled

override def supportColumnarReads(partition: InputPartition): Boolean = {
sqlConf.orcVectorizedReaderEnabled && sqlConf.wholeStageEnabled &&
Expand Down Expand Up @@ -88,7 +89,12 @@ case class OrcPartitionReaderFactory(
pushDownPredicates(filePath, conf)

val fs = filePath.getFileSystem(conf)
val readerOptions = OrcFile.readerOptions(conf).filesystem(fs)
val readerOptions = if (orcMetaCacheEnabled) {
val tail = OrcFileMeta.readTailFromCache(filePath, conf)
OrcFile.readerOptions(conf).filesystem(fs).orcTail(tail)
} else {
OrcFile.readerOptions(conf).filesystem(fs)
}
val resultedColPruneInfo =
Utils.tryWithResource(OrcFile.createReader(filePath, readerOptions)) { reader =>
OrcUtils.requestedColumnIds(
Expand Down Expand Up @@ -135,7 +141,12 @@ case class OrcPartitionReaderFactory(
pushDownPredicates(filePath, conf)

val fs = filePath.getFileSystem(conf)
val readerOptions = OrcFile.readerOptions(conf).filesystem(fs)
val readerOptions = if (orcMetaCacheEnabled) {
val tail = OrcFileMeta.readTailFromCache(filePath, conf)
OrcFile.readerOptions(conf).filesystem(fs).orcTail(tail)
} else {
OrcFile.readerOptions(conf).filesystem(fs)
}
val resultedColPruneInfo =
Utils.tryWithResource(OrcFile.createReader(filePath, readerOptions)) { reader =>
OrcUtils.requestedColumnIds(
Expand All @@ -158,6 +169,10 @@ case class OrcPartitionReaderFactory(
val taskAttemptContext = new TaskAttemptContextImpl(taskConf, attemptId)

val batchReader = new OrcColumnarBatchReader(capacity)
if (orcMetaCacheEnabled) {
val tail = OrcFileMeta.readTailFromCache(filePath, conf)
Comment thread
dongjoon-hyun marked this conversation as resolved.
batchReader.setCachedTail(tail)
}
batchReader.initialize(fileSplit, taskAttemptContext)
val requestedPartitionColIds =
Array.fill(readDataSchema.length)(-1) ++ Range(0, partitionSchema.length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import org.apache.spark.{SparkConf, SparkException}
import org.apache.spark.sql._
import org.apache.spark.sql.catalyst.TableIdentifier
import org.apache.spark.sql.execution.FileSourceScanExec
import org.apache.spark.sql.execution.datasources.{HadoopFsRelation, LogicalRelation, RecordReaderIterator}
import org.apache.spark.sql.execution.datasources.{FileMetaCacheManager, HadoopFsRelation, LogicalRelation, RecordReaderIterator}
import org.apache.spark.sql.execution.datasources.v2.BatchScanExec
import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.test.SharedSparkSession
Expand Down Expand Up @@ -742,6 +742,43 @@ abstract class OrcQuerySuite extends OrcQueryTest with SharedSparkSession {
}
}
}

test("SPARK-36516: simple select queries with file meta cache") {
withSQLConf(SQLConf.FILE_META_CACHE_ORC_ENABLED.key -> "true") {
import org.scalatest.PrivateMethodTester._
import com.github.benmanes.caffeine.cache.stats.CacheStats
Comment thread
dongjoon-hyun marked this conversation as resolved.
Outdated
val cacheStatsMethod = PrivateMethod[CacheStats](Symbol("cacheStats"))
val cleanUpMethod = PrivateMethod[Unit](Symbol("cleanUp"))
val tableName = "orc_use_meta_cache"
withTable(tableName) {
(0 until 10).map(i => (i, i.toString)).toDF("id", "value")
.write.format("orc").saveAsTable(tableName)
try {
val statsBeforeQuery = FileMetaCacheManager.invokePrivate(cacheStatsMethod())
checkAnswer(sql(s"SELECT id FROM $tableName where id > 5"),
(6 until 10).map(Row.apply(_)))
val statsAfterQuery1 = FileMetaCacheManager.invokePrivate(cacheStatsMethod())
// The 1st query triggers 4 times file meta read: 2 times related to
// push down filter and 2 times related to file read. The 1st query
// run twice: df.collect() and df.rdd.count(), so it triggers 8 times
// file meta read in total. missCount is 2 because cache is empty and
// 2 meta files need load, other 6 times will read meta from cache.
assert(statsAfterQuery1.missCount() - statsBeforeQuery.missCount() == 2)
assert(statsAfterQuery1.hitCount() - statsBeforeQuery.hitCount() == 6)
checkAnswer(sql(s"SELECT id FROM $tableName where id < 5"),
(0 until 5).map(Row.apply(_)))
val statsAfterQuery2 = FileMetaCacheManager.invokePrivate(cacheStatsMethod())
// The 2nd query also triggers 8 times file meta read in total and
// all read from meta cache, so missCount no growth and hitCount
// increase 8 times.
assert(statsAfterQuery2.missCount() - statsAfterQuery1.missCount() == 0)
assert(statsAfterQuery2.hitCount() - statsAfterQuery1.hitCount() == 8)
} finally {
FileMetaCacheManager.invokePrivate(cleanUpMethod())
}
}
}
}
}

class OrcV1QuerySuite extends OrcQuerySuite {
Expand Down