You are viewing a plain text version of this content. The canonical link for it is here.
Posted to reviews@spark.apache.org by GitBox <gi...@apache.org> on 2021/07/02 09:48:50 UTC
[GitHub] [spark] xuanyuanking opened a new pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
xuanyuanking opened a new pull request #33187:
URL: https://github.com/apache/spark/pull/33187
### What changes were proposed in this pull request?
Add the implementation for the RocksDBStateStoreProvider. It's the subclass of StateStoreProvider that leverages all the functionalities implemented in the RocksDB instance.
### Why are the changes needed?
The interface for the end-user to use the RocksDB state store.
### Does this PR introduce _any_ user-facing change?
Yes. New RocksDBStateStore can be used in their applications.
### How was this patch tested?
New UT added.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876469514
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140790/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874545800
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45200/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] HeartSaVioR commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
HeartSaVioR commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876381447
Thanks @xuanyuanking for contribution! Merged to master/3.2 branches.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876163527
```
One thing we haven't been addressed yet is documentation. End users have no idea how to use this provider. Probably we should introduce RocksDB state store provider in SS guide doc.
It's OK if you prefer to file a "blocker" JIRA issue to address documentation separately. I can approve this one then.
```
Sure, create the blocker issue SPARK-36041 for tracking the documentation.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876187425
**[Test build #140779 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140779/testReport)** for PR 33187 at commit [`1ac5835`](https://github.com/apache/spark/commit/1ac583553e05f217b224b8c07421da0568b41b25).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] viirya commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
viirya commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r665964211
##########
File path: sql/core/src/test/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreSuite.scala
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.streaming.state
+
+import java.util.UUID
+
+import scala.util.Random
+
+import org.apache.hadoop.conf.Configuration
+import org.scalatest.BeforeAndAfter
+
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.LocalSparkSession.withSparkSession
+import org.apache.spark.sql.SparkSession
+import org.apache.spark.sql.execution.streaming.StatefulOperatorStateInfo
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.types._
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+class RocksDBStateStoreSuite extends StateStoreSuiteBase[RocksDBStateStoreProvider]
+ with BeforeAndAfter {
+
+ import StateStoreTestsHelper._
+
+ test("version encoding") {
+ import RocksDBStateStoreProvider._
+
+ val provider = newStoreProvider()
+ val store = provider.getStore(0)
+ val keyRow = stringToRow("a")
+ val valueRow = intToRow(1)
+ store.put(keyRow, valueRow)
+ val iter = provider.rocksDB.iterator()
+ assert(iter.hasNext)
+ val kv = iter.next()
+
+ // Verify the version encoded in first byte of the key and value byte arrays
+ assert(Platform.getByte(kv.key, Platform.BYTE_ARRAY_OFFSET) === STATE_ENCODING_VERSION)
+ assert(Platform.getByte(kv.value, Platform.BYTE_ARRAY_OFFSET) === STATE_ENCODING_VERSION)
+ }
+
+ test("RocksDB confs are passed correctly from SparkSession to db instance") {
+ val sparkConf = new SparkConf().setMaster("local").setAppName(this.getClass.getSimpleName)
+ withSparkSession(SparkSession.builder.config(sparkConf).getOrCreate()) { spark =>
+ // Set the session confs that should be passed into RocksDB
+ val testConfs = Seq(
+ ("spark.sql.streaming.stateStore.providerClass",
+ classOf[RocksDBStateStoreProvider].getName),
+ (RocksDBConf.ROCKSDB_CONF_NAME_PREFIX + ".compactOnCommit", "true"),
+ (RocksDBConf.ROCKSDB_CONF_NAME_PREFIX + ".lockAcquireTimeoutMs", "10")
+ )
+ testConfs.foreach { case (k, v) => spark.conf.set(k, v) }
+
+ // Prepare test objects for running task on state store
+ val testRDD = spark.sparkContext.makeRDD[String](Seq("a"), 1)
+ val testSchema = StructType(Seq(StructField("key", StringType, true)))
+ val testStateInfo = StatefulOperatorStateInfo(
+ checkpointLocation = Utils.createTempDir().getAbsolutePath,
+ queryRunId = UUID.randomUUID, operatorId = 0, storeVersion = 0, numPartitions = 5)
+
+ // Create state store in a task and get the RocksDBConf from the instantiated RocksDB instance
+ val rocksDBConfInTask: RocksDBConf = testRDD.mapPartitionsWithStateStore[RocksDBConf](
+ spark.sqlContext, testStateInfo, testSchema, testSchema, None) {
+ (store: StateStore, _: Iterator[String]) =>
+ // Use reflection to get RockDB instance
Review comment:
nit: RockDB -> RocksDB
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-872876856
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874852636
Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45218/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] HeartSaVioR commented on a change in pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
HeartSaVioR commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r663608311
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ keyRow.pointTo(
+ keyBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ keyBytes.length - STATE_ENCODING_NUM_VERSION_BYTES)
+ keyRow
+ } else null
+ }
+
+ /**
+ * Decode byte array for a value to a UnsafeRow.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeValue(valueBytes: Array[Byte]): UnsafeRow = {
+ if (valueBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ valueRow.pointTo(
Review comment:
We can deduplicate here; I can deal with it in prefix scan for RocksDB state store as I'll bring broader change for state encoding.
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
Review comment:
This can be referenced by `version` so no need to be `val`.
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ keyRow.pointTo(
+ keyBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ keyBytes.length - STATE_ENCODING_NUM_VERSION_BYTES)
+ keyRow
+ } else null
+ }
+
+ /**
+ * Decode byte array for a value to a UnsafeRow.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeValue(valueBytes: Array[Byte]): UnsafeRow = {
+ if (valueBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ valueRow.pointTo(
+ valueBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ valueBytes.size - STATE_ENCODING_NUM_VERSION_BYTES)
+ valueRow
+ } else null
+ }
+
+ /**
+ * Decode pair of key-value byte arrays in a pair of key-value UnsafeRows.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decode(byteArrayTuple: ByteArrayPair): UnsafeRowPair = {
+ rowTuple.withRows(decodeKey(byteArrayTuple.key), decodeValue(byteArrayTuple.value))
+ }
+ }
+}
+
+object RocksDBStateStoreProvider {
+ // Version as a single byte that specifies the encoding of the row data in RocksDB
+ val STATE_ENCODING_NUM_VERSION_BYTES = 1
+ val STATE_ENCODING_VERSION: Byte = 0
+
+ // Native operation latencies report as latency per 1000 calls
+ // as SQLMetrics support ms latency whereas RocksDB reports it in microseconds.
+ val CUSTOM_METRIC_GET_TIME = StateStoreCustomTimingMetric(
+ "rocksdbGetLatency", "RocksDB: avg get latency (per 1000 calls)")
+ val CUSTOM_METRIC_PUT_TIME = StateStoreCustomTimingMetric(
+ "rocksdbPutLatency", "RocksDB: avg put latency (per 1000 calls)")
+
+ // Commit latency detailed breakdown
+ val CUSTOM_METRIC_WRITEBATCH_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitWriteBatchLatency", "RocksDB: commit - write batch time")
+ val CUSTOM_METRIC_FLUSH_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitFlushLatency", "RocksDB: commit - flush time")
+ val CUSTOM_METRIC_PAUSE_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitPauseLatency", "RocksDB: commit - pause bg time")
+ val CUSTOM_METRIC_CHECKPOINT_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitCheckpointLatency", "RocksDB: commit - checkpoint time")
+ val CUSTOM_METRIC_FILESYNC_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitPauseBgTime", "RocksDB: commit - file sync time")
+ val CUSTOM_METRIC_FILES_COPIED = StateStoreCustomSizeMetric(
+ "rocksdbFilesCopied", "RocksDB: file manager - files copied")
+ val CUSTOM_METRIC_BYTES_COPIED = StateStoreCustomSizeMetric(
+ "rocksdbBytesCopied", "RocksDB: file manager - bytes copied")
+ val CUSTOM_METRIC_FILES_REUSED = StateStoreCustomSizeMetric(
+ "rocksdbFilesReused", "RocksDB: file manager - files reused")
+ val CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED = StateStoreCustomSizeMetric(
+ "rocksdbZipFileBytesUncompressed", "RocksDB: file manager - uncompressed zip file bytes")
+
+ // Total SST file size
+ val CUSTOM_METRIC_SST_FILE_SIZE = StateStoreCustomSizeMetric(
+ "rocksdbSstFileSize", "RocksDB: size of all SST files")
+
Review comment:
nit: remove one of two empty lines
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
Review comment:
This simply works as there's only one version (version 0) - we'll need to check the version when we add a new state encoding version.
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
Review comment:
I thought the characters (`=,()`) being used in `storeIdStr` are odd, but this works in MacOS and I think it would also work in Linux file systems, so OK.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] viirya commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
viirya commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r665961325
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ require(value != null, "Cannot put a null value")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pause"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ keyRow.pointTo(
+ keyBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ keyBytes.length - STATE_ENCODING_NUM_VERSION_BYTES)
+ keyRow
+ } else null
Review comment:
nit: I rarely see style like this in Spark.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874491268
Yes, finally!
Addressed the comments and rebased the code.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876225312
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45291/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876206723
Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45291/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] viirya commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
viirya commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r665962718
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ require(value != null, "Cannot put a null value")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pause"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ keyRow.pointTo(
+ keyBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ keyBytes.length - STATE_ENCODING_NUM_VERSION_BYTES)
+ keyRow
+ } else null
+ }
+
+ /**
+ * Decode byte array for a value to a UnsafeRow.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeValue(valueBytes: Array[Byte]): UnsafeRow = {
+ if (valueBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ valueRow.pointTo(
+ valueBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ valueBytes.size - STATE_ENCODING_NUM_VERSION_BYTES)
+ valueRow
+ } else null
+ }
+
+ /**
+ * Decode pair of key-value byte arrays in a pair of key-value UnsafeRows.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decode(byteArrayTuple: ByteArrayPair): UnsafeRowPair = {
+ rowTuple.withRows(decodeKey(byteArrayTuple.key), decodeValue(byteArrayTuple.value))
+ }
+ }
+}
+
+object RocksDBStateStoreProvider {
+ // Version as a single byte that specifies the encoding of the row data in RocksDB
+ val STATE_ENCODING_NUM_VERSION_BYTES = 1
+ val STATE_ENCODING_VERSION: Byte = 0
+
+ // Native operation latencies report as latency per 1000 calls
+ // as SQLMetrics support ms latency whereas RocksDB reports it in microseconds.
+ val CUSTOM_METRIC_GET_TIME = StateStoreCustomTimingMetric(
+ "rocksdbGetLatency", "RocksDB: avg get latency (per 1000 calls)")
+ val CUSTOM_METRIC_PUT_TIME = StateStoreCustomTimingMetric(
+ "rocksdbPutLatency", "RocksDB: avg put latency (per 1000 calls)")
+
+ // Commit latency detailed breakdown
+ val CUSTOM_METRIC_WRITEBATCH_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitWriteBatchLatency", "RocksDB: commit - write batch time")
+ val CUSTOM_METRIC_FLUSH_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitFlushLatency", "RocksDB: commit - flush time")
+ val CUSTOM_METRIC_PAUSE_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitPauseLatency", "RocksDB: commit - pause bg time")
+ val CUSTOM_METRIC_CHECKPOINT_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitCheckpointLatency", "RocksDB: commit - checkpoint time")
+ val CUSTOM_METRIC_FILESYNC_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitPauseBgTime", "RocksDB: commit - file sync time")
Review comment:
rocksdbCommitPauseBgTime? Or FileSyncTime?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874490690
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-872872769
**[Test build #140577 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140577/testReport)** for PR 33187 at commit [`af24bd9`](https://github.com/apache/spark/commit/af24bd9eca92eaa66106da4d6f52fbc6d7902dbb).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r665896729
##########
File path: sql/core/src/test/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreSuite.scala
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.streaming.state
+
+import java.util.UUID
+
+import scala.util.Random
+
+import org.apache.hadoop.conf.Configuration
+import org.scalatest.BeforeAndAfter
+
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.LocalSparkSession.withSparkSession
+import org.apache.spark.sql.SparkSession
+import org.apache.spark.sql.execution.streaming.StatefulOperatorStateInfo
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.types._
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+class RocksDBStateStoreSuite extends StateStoreSuiteBase[RocksDBStateStoreProvider]
+ with BeforeAndAfter {
+
+ import StateStoreTestsHelper._
+
+ test("version encoding") {
+ import RocksDBStateStoreProvider._
+
+ val provider = newStoreProvider()
+ val store = provider.getStore(0)
+ val keyRow = stringToRow("a")
+ val valueRow = intToRow(1)
+ store.put(keyRow, valueRow)
+ val iter = provider.rocksDB.iterator()
+ assert(iter.hasNext)
+ val kv = iter.next()
+
+ // Verify the version encoded in first byte of the key and value byte arrays
+ assert(Platform.getByte(kv.key, Platform.BYTE_ARRAY_OFFSET) === STATE_ENCODING_VERSION)
+ assert(Platform.getByte(kv.value, Platform.BYTE_ARRAY_OFFSET) === STATE_ENCODING_VERSION)
+ }
+
+ test("RocksDB confs are passed correctly from SparkSession to db instance") {
+ val sparkConf = new SparkConf().setMaster("local").setAppName(this.getClass.getSimpleName)
+ withSparkSession(SparkSession.builder.config(sparkConf).getOrCreate()) { spark =>
+ // Set the session confs that should be passed into RocksDB
+ val testConfs = Seq(
+ ("spark.sql.streaming.stateStore.providerClass",
+ classOf[RocksDBStateStoreProvider].getName),
+ (RocksDBConf.ROCKSDB_CONF_NAME_PREFIX + ".compactOnCommit", "true"),
+ (RocksDBConf.ROCKSDB_CONF_NAME_PREFIX + ".lockAcquireTimeoutMs", "10")
+ )
+ testConfs.foreach { case (k, v) => spark.conf.set(k, v) }
+
+ // Prepare test objects for running task on state store
+ val testRDD = spark.sparkContext.makeRDD[String](Seq("a"), 1)
+ val testSchema = StructType(Seq(StructField("key", StringType, true)))
+ val testStateInfo = StatefulOperatorStateInfo(
+ checkpointLocation = Utils.createTempDir().getAbsolutePath,
+ queryRunId = UUID.randomUUID, operatorId = 0, storeVersion = 0, numPartitions = 5)
+
+ // Create state store in a task and get the RocksDBConf from the instantiated RocksDB instance
+ val rocksDBConfInTask: RocksDBConf = testRDD.mapPartitionsWithStateStore[RocksDBConf](
+ spark.sqlContext, testStateInfo, testSchema, testSchema, None) {
+ (store: StateStore, _: Iterator[String]) =>
+ // Use reflection to get RockDB instance
+ val dbInstanceMethod =
+ store.getClass.getMethods.filter(_.getName.contains("dbInstance")).head
+ Iterator(dbInstanceMethod.invoke(store).asInstanceOf[RocksDB].conf)
+ }.collect().head
+
+ // Verify the confs are same as those configured in the session conf
+ assert(rocksDBConfInTask.compactOnCommit == true)
+ assert(rocksDBConfInTask.lockAcquireTimeoutMs == 10L)
+ }
+ }
+
+ test("rocksdb file manager metrics exposed") {
+ import RocksDBStateStoreProvider._
+ def getCustomMetric(metrics: StateStoreMetrics, customMetric: StateStoreCustomMetric): Long = {
+ val metricPair = metrics.customMetrics.find(_._1.name == customMetric.name)
+ assert(metricPair.isDefined)
+ metricPair.get._2
+ }
+
+ val provider = newStoreProvider()
+ val store = provider.getStore(0)
+ // Verify state after updating
+ put(store, "a", 1)
+ assert(get(store, "a") === Some(1))
+ assert(store.commit() === 1)
+ assert(store.hasCommitted)
+ val storeMetrics = store.metrics
+ assert(storeMetrics.numKeys === 1)
+ assert(getCustomMetric(storeMetrics, CUSTOM_METRIC_FILES_COPIED) == 1L)
Review comment:
Agree, will update in the next commit.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA removed a comment on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-872872769
**[Test build #140577 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140577/testReport)** for PR 33187 at commit [`af24bd9`](https://github.com/apache/spark/commit/af24bd9eca92eaa66106da4d6f52fbc6d7902dbb).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r664265726
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ keyRow.pointTo(
+ keyBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ keyBytes.length - STATE_ENCODING_NUM_VERSION_BYTES)
+ keyRow
+ } else null
+ }
+
+ /**
+ * Decode byte array for a value to a UnsafeRow.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeValue(valueBytes: Array[Byte]): UnsafeRow = {
+ if (valueBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ valueRow.pointTo(
Review comment:
Copy that.
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ keyRow.pointTo(
+ keyBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ keyBytes.length - STATE_ENCODING_NUM_VERSION_BYTES)
+ keyRow
+ } else null
+ }
+
+ /**
+ * Decode byte array for a value to a UnsafeRow.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeValue(valueBytes: Array[Byte]): UnsafeRow = {
+ if (valueBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ valueRow.pointTo(
+ valueBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ valueBytes.size - STATE_ENCODING_NUM_VERSION_BYTES)
+ valueRow
+ } else null
+ }
+
+ /**
+ * Decode pair of key-value byte arrays in a pair of key-value UnsafeRows.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decode(byteArrayTuple: ByteArrayPair): UnsafeRowPair = {
+ rowTuple.withRows(decodeKey(byteArrayTuple.key), decodeValue(byteArrayTuple.value))
+ }
+ }
+}
+
+object RocksDBStateStoreProvider {
+ // Version as a single byte that specifies the encoding of the row data in RocksDB
+ val STATE_ENCODING_NUM_VERSION_BYTES = 1
+ val STATE_ENCODING_VERSION: Byte = 0
+
+ // Native operation latencies report as latency per 1000 calls
+ // as SQLMetrics support ms latency whereas RocksDB reports it in microseconds.
+ val CUSTOM_METRIC_GET_TIME = StateStoreCustomTimingMetric(
+ "rocksdbGetLatency", "RocksDB: avg get latency (per 1000 calls)")
+ val CUSTOM_METRIC_PUT_TIME = StateStoreCustomTimingMetric(
+ "rocksdbPutLatency", "RocksDB: avg put latency (per 1000 calls)")
+
+ // Commit latency detailed breakdown
+ val CUSTOM_METRIC_WRITEBATCH_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitWriteBatchLatency", "RocksDB: commit - write batch time")
+ val CUSTOM_METRIC_FLUSH_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitFlushLatency", "RocksDB: commit - flush time")
+ val CUSTOM_METRIC_PAUSE_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitPauseLatency", "RocksDB: commit - pause bg time")
+ val CUSTOM_METRIC_CHECKPOINT_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitCheckpointLatency", "RocksDB: commit - checkpoint time")
+ val CUSTOM_METRIC_FILESYNC_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitPauseBgTime", "RocksDB: commit - file sync time")
+ val CUSTOM_METRIC_FILES_COPIED = StateStoreCustomSizeMetric(
+ "rocksdbFilesCopied", "RocksDB: file manager - files copied")
+ val CUSTOM_METRIC_BYTES_COPIED = StateStoreCustomSizeMetric(
+ "rocksdbBytesCopied", "RocksDB: file manager - bytes copied")
+ val CUSTOM_METRIC_FILES_REUSED = StateStoreCustomSizeMetric(
+ "rocksdbFilesReused", "RocksDB: file manager - files reused")
+ val CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED = StateStoreCustomSizeMetric(
+ "rocksdbZipFileBytesUncompressed", "RocksDB: file manager - uncompressed zip file bytes")
+
+ // Total SST file size
+ val CUSTOM_METRIC_SST_FILE_SIZE = StateStoreCustomSizeMetric(
+ "rocksdbSstFileSize", "RocksDB: size of all SST files")
+
Review comment:
Thanks, done in 9ddbf58
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA removed a comment on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-872980326
**[Test build #140587 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140587/testReport)** for PR 33187 at commit [`f59a9e9`](https://github.com/apache/spark/commit/f59a9e9ec9fab6b66b06216c8926e9da5d172ddf).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-872876800
**[Test build #140577 has finished](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140577/testReport)** for PR 33187 at commit [`af24bd9`](https://github.com/apache/spark/commit/af24bd9eca92eaa66106da4d6f52fbc6d7902dbb).
* This patch **fails to build**.
* This patch merges cleanly.
* This patch adds the following public classes _(experimental)_:
* ` class RocksDBStateStore(val lastVersion: Long) extends StateStore `
* ` class StateEncoder `
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874545800
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876186338
Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45291/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874586692
**[Test build #140697 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140697/testReport)** for PR 33187 at commit [`a886567`](https://github.com/apache/spark/commit/a886567bdfa5eb31f69ef58cb0f21288c04f28af).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] HeartSaVioR commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
HeartSaVioR commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874415015
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874787722
**[Test build #140707 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140707/testReport)** for PR 33187 at commit [`e63a380`](https://github.com/apache/spark/commit/e63a380c599052cabc130ac2d7794689f621323a).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] viirya commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
viirya commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r665948535
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ require(value != null, "Cannot put a null value")
+ logDebug(s"Storing $key => $value")
Review comment:
Not sure if this log debug is necessary. Especially key/value are unsafe rows.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874627231
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140685/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876436929
Thanks @HeartSaVioR and @viirya for all the help and review!
Finally, all the major implementations of the RocksDB state store have been merged!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r664265493
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
Review comment:
Yea, agree. For the new encoding version, we should have branches here for different versions.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876453078
**[Test build #140790 has finished](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140790/testReport)** for PR 33187 at commit [`b6a9d0a`](https://github.com/apache/spark/commit/b6a9d0ac78e85c1c411d67ae48417de02de85093).
* This patch passes all tests.
* This patch merges cleanly.
* This patch adds no public classes.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874490690
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874490690
**[Test build #140685 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140685/testReport)** for PR 33187 at commit [`9ddbf58`](https://github.com/apache/spark/commit/9ddbf589035dc9450944ca7c1bc52fd5b419d51d).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874491268
Yes, finally!
Addressed the comments and rebased the code.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876225312
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45291/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] HeartSaVioR commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
HeartSaVioR commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874466167
Finally we only have this one for RocksDB state store provider! Please rebase this so that we can continue.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r666022602
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ require(value != null, "Cannot put a null value")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pause"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ keyRow.pointTo(
+ keyBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ keyBytes.length - STATE_ENCODING_NUM_VERSION_BYTES)
+ keyRow
+ } else null
+ }
+
+ /**
+ * Decode byte array for a value to a UnsafeRow.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeValue(valueBytes: Array[Byte]): UnsafeRow = {
+ if (valueBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ valueRow.pointTo(
+ valueBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ valueBytes.size - STATE_ENCODING_NUM_VERSION_BYTES)
+ valueRow
+ } else null
+ }
+
+ /**
+ * Decode pair of key-value byte arrays in a pair of key-value UnsafeRows.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decode(byteArrayTuple: ByteArrayPair): UnsafeRowPair = {
+ rowTuple.withRows(decodeKey(byteArrayTuple.key), decodeValue(byteArrayTuple.value))
+ }
+ }
+}
+
+object RocksDBStateStoreProvider {
+ // Version as a single byte that specifies the encoding of the row data in RocksDB
+ val STATE_ENCODING_NUM_VERSION_BYTES = 1
+ val STATE_ENCODING_VERSION: Byte = 0
+
+ // Native operation latencies report as latency per 1000 calls
+ // as SQLMetrics support ms latency whereas RocksDB reports it in microseconds.
+ val CUSTOM_METRIC_GET_TIME = StateStoreCustomTimingMetric(
+ "rocksdbGetLatency", "RocksDB: avg get latency (per 1000 calls)")
+ val CUSTOM_METRIC_PUT_TIME = StateStoreCustomTimingMetric(
+ "rocksdbPutLatency", "RocksDB: avg put latency (per 1000 calls)")
+
+ // Commit latency detailed breakdown
+ val CUSTOM_METRIC_WRITEBATCH_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitWriteBatchLatency", "RocksDB: commit - write batch time")
+ val CUSTOM_METRIC_FLUSH_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitFlushLatency", "RocksDB: commit - flush time")
+ val CUSTOM_METRIC_PAUSE_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitPauseLatency", "RocksDB: commit - pause bg time")
+ val CUSTOM_METRIC_CHECKPOINT_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitCheckpointLatency", "RocksDB: commit - checkpoint time")
+ val CUSTOM_METRIC_FILESYNC_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitPauseBgTime", "RocksDB: commit - file sync time")
Review comment:
Yea, changed to rocksdbFileSyncTime
##########
File path: sql/core/src/test/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreSuite.scala
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.streaming.state
+
+import java.util.UUID
+
+import scala.util.Random
+
+import org.apache.hadoop.conf.Configuration
+import org.scalatest.BeforeAndAfter
+
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.LocalSparkSession.withSparkSession
+import org.apache.spark.sql.SparkSession
+import org.apache.spark.sql.execution.streaming.StatefulOperatorStateInfo
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.types._
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+class RocksDBStateStoreSuite extends StateStoreSuiteBase[RocksDBStateStoreProvider]
+ with BeforeAndAfter {
+
+ import StateStoreTestsHelper._
+
+ test("version encoding") {
+ import RocksDBStateStoreProvider._
+
+ val provider = newStoreProvider()
+ val store = provider.getStore(0)
+ val keyRow = stringToRow("a")
+ val valueRow = intToRow(1)
+ store.put(keyRow, valueRow)
+ val iter = provider.rocksDB.iterator()
+ assert(iter.hasNext)
+ val kv = iter.next()
+
+ // Verify the version encoded in first byte of the key and value byte arrays
+ assert(Platform.getByte(kv.key, Platform.BYTE_ARRAY_OFFSET) === STATE_ENCODING_VERSION)
+ assert(Platform.getByte(kv.value, Platform.BYTE_ARRAY_OFFSET) === STATE_ENCODING_VERSION)
+ }
+
+ test("RocksDB confs are passed correctly from SparkSession to db instance") {
+ val sparkConf = new SparkConf().setMaster("local").setAppName(this.getClass.getSimpleName)
+ withSparkSession(SparkSession.builder.config(sparkConf).getOrCreate()) { spark =>
+ // Set the session confs that should be passed into RocksDB
+ val testConfs = Seq(
+ ("spark.sql.streaming.stateStore.providerClass",
+ classOf[RocksDBStateStoreProvider].getName),
+ (RocksDBConf.ROCKSDB_CONF_NAME_PREFIX + ".compactOnCommit", "true"),
+ (RocksDBConf.ROCKSDB_CONF_NAME_PREFIX + ".lockAcquireTimeoutMs", "10")
+ )
+ testConfs.foreach { case (k, v) => spark.conf.set(k, v) }
+
+ // Prepare test objects for running task on state store
+ val testRDD = spark.sparkContext.makeRDD[String](Seq("a"), 1)
+ val testSchema = StructType(Seq(StructField("key", StringType, true)))
+ val testStateInfo = StatefulOperatorStateInfo(
+ checkpointLocation = Utils.createTempDir().getAbsolutePath,
+ queryRunId = UUID.randomUUID, operatorId = 0, storeVersion = 0, numPartitions = 5)
+
+ // Create state store in a task and get the RocksDBConf from the instantiated RocksDB instance
+ val rocksDBConfInTask: RocksDBConf = testRDD.mapPartitionsWithStateStore[RocksDBConf](
+ spark.sqlContext, testStateInfo, testSchema, testSchema, None) {
+ (store: StateStore, _: Iterator[String]) =>
+ // Use reflection to get RockDB instance
Review comment:
Thanks, done in the next commit.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-875006946
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140707/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874545800
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45200/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] HeartSaVioR commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
HeartSaVioR commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874415015
NOTE: This might have some post-review comments from #32934 .
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-873120726
**[Test build #140587 has finished](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140587/testReport)** for PR 33187 at commit [`f59a9e9`](https://github.com/apache/spark/commit/f59a9e9ec9fab6b66b06216c8926e9da5d172ddf).
* This patch **fails Spark unit tests**.
* This patch merges cleanly.
* This patch adds no public classes.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] HeartSaVioR closed pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
HeartSaVioR closed pull request #33187:
URL: https://github.com/apache/spark/pull/33187
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-873013353
Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45098/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r664264875
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
Review comment:
Yes, the dir contains `=()` works in the Linux file system.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876282101
**[Test build #140790 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140790/testReport)** for PR 33187 at commit [`b6a9d0a`](https://github.com/apache/spark/commit/b6a9d0ac78e85c1c411d67ae48417de02de85093).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-873038579
Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45098/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] HeartSaVioR commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
HeartSaVioR commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876230649
I'm OK to wait for addressing these comments - I guess we can still merge this in this week, then it's OK for me.
I'm already working on rebasing #33038 on top of this PR. Looks like I need to bring another bunch of lines in #33038 to address RocksDB state store.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876282101
**[Test build #140790 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140790/testReport)** for PR 33187 at commit [`b6a9d0a`](https://github.com/apache/spark/commit/b6a9d0ac78e85c1c411d67ae48417de02de85093).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r665896298
##########
File path: sql/core/src/test/scala/org/apache/spark/sql/execution/streaming/state/StateStoreSuite.scala
##########
@@ -235,6 +236,162 @@ class StateStoreSuite extends StateStoreSuiteBase[HDFSBackedStateStoreProvider]
assert(getSizeOfStateForCurrentVersion(store.metrics) > noDataMemoryUsed)
}
+ test("maintenance") {
Review comment:
Yes, since these tests have the assumption that the provider have a `baseDir`
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874787722
**[Test build #140707 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140707/testReport)** for PR 33187 at commit [`e63a380`](https://github.com/apache/spark/commit/e63a380c599052cabc130ac2d7794689f621323a).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-872880158
Kubernetes integration test unable to build dist.
exiting with code: 1
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45088/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] viirya commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
viirya commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r665951347
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ require(value != null, "Cannot put a null value")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
Review comment:
"after already committed" -> "after already committed or aborted".
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] viirya commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
viirya commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r665956648
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ require(value != null, "Cannot put a null value")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pause"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
Review comment:
I guess this is a typo. "the" -> "that".
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876346244
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45303/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874536551
Kubernetes integration test unable to build dist.
exiting with code: 1
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45200/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874662509
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r664264022
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
Review comment:
Thanks, done in 9ddbf58
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876346244
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45303/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874651648
**[Test build #140697 has finished](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140697/testReport)** for PR 33187 at commit [`a886567`](https://github.com/apache/spark/commit/a886567bdfa5eb31f69ef58cb0f21288c04f28af).
* This patch **fails Spark unit tests**.
* This patch merges cleanly.
* This patch adds no public classes.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874662510
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45208/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] HeartSaVioR commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
HeartSaVioR commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r665856232
##########
File path: sql/core/src/test/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreSuite.scala
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.streaming.state
+
+import java.util.UUID
+
+import scala.util.Random
+
+import org.apache.hadoop.conf.Configuration
+import org.scalatest.BeforeAndAfter
+
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.LocalSparkSession.withSparkSession
+import org.apache.spark.sql.SparkSession
+import org.apache.spark.sql.execution.streaming.StatefulOperatorStateInfo
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.types._
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+class RocksDBStateStoreSuite extends StateStoreSuiteBase[RocksDBStateStoreProvider]
+ with BeforeAndAfter {
+
+ import StateStoreTestsHelper._
+
+ test("version encoding") {
+ import RocksDBStateStoreProvider._
+
+ val provider = newStoreProvider()
+ val store = provider.getStore(0)
+ val keyRow = stringToRow("a")
+ val valueRow = intToRow(1)
+ store.put(keyRow, valueRow)
+ val iter = provider.rocksDB.iterator()
+ assert(iter.hasNext)
+ val kv = iter.next()
+
+ // Verify the version encoded in first byte of the key and value byte arrays
+ assert(Platform.getByte(kv.key, Platform.BYTE_ARRAY_OFFSET) === STATE_ENCODING_VERSION)
+ assert(Platform.getByte(kv.value, Platform.BYTE_ARRAY_OFFSET) === STATE_ENCODING_VERSION)
+ }
+
+ test("RocksDB confs are passed correctly from SparkSession to db instance") {
+ val sparkConf = new SparkConf().setMaster("local").setAppName(this.getClass.getSimpleName)
+ withSparkSession(SparkSession.builder.config(sparkConf).getOrCreate()) { spark =>
+ // Set the session confs that should be passed into RocksDB
+ val testConfs = Seq(
+ ("spark.sql.streaming.stateStore.providerClass",
+ classOf[RocksDBStateStoreProvider].getName),
+ (RocksDBConf.ROCKSDB_CONF_NAME_PREFIX + ".compactOnCommit", "true"),
+ (RocksDBConf.ROCKSDB_CONF_NAME_PREFIX + ".lockAcquireTimeoutMs", "10")
+ )
+ testConfs.foreach { case (k, v) => spark.conf.set(k, v) }
+
+ // Prepare test objects for running task on state store
+ val testRDD = spark.sparkContext.makeRDD[String](Seq("a"), 1)
+ val testSchema = StructType(Seq(StructField("key", StringType, true)))
+ val testStateInfo = StatefulOperatorStateInfo(
+ checkpointLocation = Utils.createTempDir().getAbsolutePath,
+ queryRunId = UUID.randomUUID, operatorId = 0, storeVersion = 0, numPartitions = 5)
+
+ // Create state store in a task and get the RocksDBConf from the instantiated RocksDB instance
+ val rocksDBConfInTask: RocksDBConf = testRDD.mapPartitionsWithStateStore[RocksDBConf](
+ spark.sqlContext, testStateInfo, testSchema, testSchema, None) {
+ (store: StateStore, _: Iterator[String]) =>
+ // Use reflection to get RockDB instance
+ val dbInstanceMethod =
+ store.getClass.getMethods.filter(_.getName.contains("dbInstance")).head
+ Iterator(dbInstanceMethod.invoke(store).asInstanceOf[RocksDB].conf)
+ }.collect().head
+
+ // Verify the confs are same as those configured in the session conf
+ assert(rocksDBConfInTask.compactOnCommit == true)
+ assert(rocksDBConfInTask.lockAcquireTimeoutMs == 10L)
+ }
+ }
+
+ test("rocksdb file manager metrics exposed") {
+ import RocksDBStateStoreProvider._
+ def getCustomMetric(metrics: StateStoreMetrics, customMetric: StateStoreCustomMetric): Long = {
+ val metricPair = metrics.customMetrics.find(_._1.name == customMetric.name)
+ assert(metricPair.isDefined)
+ metricPair.get._2
+ }
+
+ val provider = newStoreProvider()
+ val store = provider.getStore(0)
+ // Verify state after updating
+ put(store, "a", 1)
+ assert(get(store, "a") === Some(1))
+ assert(store.commit() === 1)
+ assert(store.hasCommitted)
+ val storeMetrics = store.metrics
+ assert(storeMetrics.numKeys === 1)
+ assert(getCustomMetric(storeMetrics, CUSTOM_METRIC_FILES_COPIED) == 1L)
Review comment:
nit: I guess we can loose the condition via `> 0L`. "The number of files is 1" doesn't seem to something we need to check.
##########
File path: sql/core/src/test/scala/org/apache/spark/sql/execution/streaming/state/StateStoreSuite.scala
##########
@@ -235,6 +236,162 @@ class StateStoreSuite extends StateStoreSuiteBase[HDFSBackedStateStoreProvider]
assert(getSizeOfStateForCurrentVersion(store.metrics) > noDataMemoryUsed)
}
+ test("maintenance") {
Review comment:
Just a head-up: "maintenance" and "snapshotting" are moved from StateStoreSuiteBase to StateStoreSuite as these tests cannot be applied to RocksDB state store provider.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874627231
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140685/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874623325
Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45208/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-873126439
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140587/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876382655
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140779/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874823825
Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45218/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874586692
**[Test build #140697 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140697/testReport)** for PR 33187 at commit [`a886567`](https://github.com/apache/spark/commit/a886567bdfa5eb31f69ef58cb0f21288c04f28af).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-875006946
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140707/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876469514
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140790/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-872980326
**[Test build #140587 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140587/testReport)** for PR 33187 at commit [`f59a9e9`](https://github.com/apache/spark/commit/f59a9e9ec9fab6b66b06216c8926e9da5d172ddf).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876335332
Kubernetes integration test unable to build dist.
exiting with code: 1
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45303/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-873050636
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45098/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876382655
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140779/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-873126439
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140587/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876187425
**[Test build #140779 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140779/testReport)** for PR 33187 at commit [`1ac5835`](https://github.com/apache/spark/commit/1ac583553e05f217b224b8c07421da0568b41b25).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874852703
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45218/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-873050636
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45098/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r666022181
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ require(value != null, "Cannot put a null value")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pause"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ keyRow.pointTo(
+ keyBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ keyBytes.length - STATE_ENCODING_NUM_VERSION_BYTES)
+ keyRow
+ } else null
Review comment:
Make sense, done in the next commit.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r664264022
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
Review comment:
Thanks, done in 9ddbf58
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
Review comment:
Yes, the dir contains `=()` works in the Linux file system.
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
Review comment:
Yea, agree. For the new encoding version, we should have branches here for different versions.
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ keyRow.pointTo(
+ keyBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ keyBytes.length - STATE_ENCODING_NUM_VERSION_BYTES)
+ keyRow
+ } else null
+ }
+
+ /**
+ * Decode byte array for a value to a UnsafeRow.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeValue(valueBytes: Array[Byte]): UnsafeRow = {
+ if (valueBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ valueRow.pointTo(
Review comment:
Copy that.
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,329 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(val lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ verify(value != null, "Value cannot be null")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pauseBg"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
+ * row is encoded in the rest of the byte array. Currently, the default version is 0,
+ *
+ * VERSION 0: [ VERSION (1 byte) | ROW (N bytes) ]
+ * The bytes of a UnsafeRow is written unmodified to starting from offset 1
+ * (offset 0 is the version byte of value 0). That is, if the unsafe row has N bytes,
+ * then the generated array byte will be N+1 bytes.
+ */
+ class StateEncoder {
+ import RocksDBStateStoreProvider._
+
+ // Reusable objects
+ private val keyRow = new UnsafeRow(keySchema.size)
+ private val valueRow = new UnsafeRow(valueSchema.size)
+ private val rowTuple = new UnsafeRowPair()
+
+ /**
+ * Encode the UnsafeRow of N bytes as a N+1 byte array.
+ * @note This creates a new byte array and memcopies the UnsafeRow to the new array.
+ */
+ def encode(row: UnsafeRow): Array[Byte] = {
+ val bytesToEncode = row.getBytes
+ val encodedBytes = new Array[Byte](bytesToEncode.length + STATE_ENCODING_NUM_VERSION_BYTES)
+ Platform.putByte(encodedBytes, Platform.BYTE_ARRAY_OFFSET, STATE_ENCODING_VERSION)
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way to memcopy b/w byte arrays. See Platform.
+ Platform.copyMemory(
+ bytesToEncode, Platform.BYTE_ARRAY_OFFSET,
+ encodedBytes, Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ bytesToEncode.length)
+ encodedBytes
+ }
+
+ /**
+ * Decode byte array for a key to a UnsafeRow.
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeKey(keyBytes: Array[Byte]): UnsafeRow = {
+ if (keyBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ keyRow.pointTo(
+ keyBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ keyBytes.length - STATE_ENCODING_NUM_VERSION_BYTES)
+ keyRow
+ } else null
+ }
+
+ /**
+ * Decode byte array for a value to a UnsafeRow.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decodeValue(valueBytes: Array[Byte]): UnsafeRow = {
+ if (valueBytes != null) {
+ // Platform.BYTE_ARRAY_OFFSET is the recommended way refer to the 1st offset. See Platform.
+ valueRow.pointTo(
+ valueBytes,
+ Platform.BYTE_ARRAY_OFFSET + STATE_ENCODING_NUM_VERSION_BYTES,
+ valueBytes.size - STATE_ENCODING_NUM_VERSION_BYTES)
+ valueRow
+ } else null
+ }
+
+ /**
+ * Decode pair of key-value byte arrays in a pair of key-value UnsafeRows.
+ *
+ * @note The UnsafeRow returned is reused across calls, and the UnsafeRow just points to
+ * the given byte array.
+ */
+ def decode(byteArrayTuple: ByteArrayPair): UnsafeRowPair = {
+ rowTuple.withRows(decodeKey(byteArrayTuple.key), decodeValue(byteArrayTuple.value))
+ }
+ }
+}
+
+object RocksDBStateStoreProvider {
+ // Version as a single byte that specifies the encoding of the row data in RocksDB
+ val STATE_ENCODING_NUM_VERSION_BYTES = 1
+ val STATE_ENCODING_VERSION: Byte = 0
+
+ // Native operation latencies report as latency per 1000 calls
+ // as SQLMetrics support ms latency whereas RocksDB reports it in microseconds.
+ val CUSTOM_METRIC_GET_TIME = StateStoreCustomTimingMetric(
+ "rocksdbGetLatency", "RocksDB: avg get latency (per 1000 calls)")
+ val CUSTOM_METRIC_PUT_TIME = StateStoreCustomTimingMetric(
+ "rocksdbPutLatency", "RocksDB: avg put latency (per 1000 calls)")
+
+ // Commit latency detailed breakdown
+ val CUSTOM_METRIC_WRITEBATCH_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitWriteBatchLatency", "RocksDB: commit - write batch time")
+ val CUSTOM_METRIC_FLUSH_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitFlushLatency", "RocksDB: commit - flush time")
+ val CUSTOM_METRIC_PAUSE_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitPauseLatency", "RocksDB: commit - pause bg time")
+ val CUSTOM_METRIC_CHECKPOINT_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitCheckpointLatency", "RocksDB: commit - checkpoint time")
+ val CUSTOM_METRIC_FILESYNC_TIME = StateStoreCustomTimingMetric(
+ "rocksdbCommitPauseBgTime", "RocksDB: commit - file sync time")
+ val CUSTOM_METRIC_FILES_COPIED = StateStoreCustomSizeMetric(
+ "rocksdbFilesCopied", "RocksDB: file manager - files copied")
+ val CUSTOM_METRIC_BYTES_COPIED = StateStoreCustomSizeMetric(
+ "rocksdbBytesCopied", "RocksDB: file manager - bytes copied")
+ val CUSTOM_METRIC_FILES_REUSED = StateStoreCustomSizeMetric(
+ "rocksdbFilesReused", "RocksDB: file manager - files reused")
+ val CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED = StateStoreCustomSizeMetric(
+ "rocksdbZipFileBytesUncompressed", "RocksDB: file manager - uncompressed zip file bytes")
+
+ // Total SST file size
+ val CUSTOM_METRIC_SST_FILE_SIZE = StateStoreCustomSizeMetric(
+ "rocksdbSstFileSize", "RocksDB: size of all SST files")
+
Review comment:
Thanks, done in 9ddbf58
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-872902570
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45088/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r666022065
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ require(value != null, "Cannot put a null value")
+ logDebug(s"Storing $key => $value")
+ rocksDB.put(encoder.encode(key), encoder.encode(value))
+ }
+
+ override def remove(key: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot remove after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ rocksDB.remove(encoder.encode(key))
+ }
+
+ override def getRange(
+ start: Option[UnsafeRow],
+ end: Option[UnsafeRow]): Iterator[UnsafeRowPair] = {
+ verify(state == UPDATING, "Cannot call getRange() after already committed or aborted")
+ iterator()
+ }
+
+ override def iterator(): Iterator[UnsafeRowPair] = {
+ rocksDB.iterator().map { kv =>
+ val rowPair = encoder.decode(kv)
+ if (!isValidated && rowPair.value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ rowPair.key, keySchema, rowPair.value, valueSchema, storeConf)
+ isValidated = true
+ }
+ rowPair
+ }
+ }
+
+ override def commit(): Long = synchronized {
+ verify(state == UPDATING, "Cannot commit after already committed or aborted")
+ val newVersion = rocksDB.commit()
+ state = COMMITTED
+ logInfo(s"Committed $newVersion for $id")
+ newVersion
+ }
+
+ override def abort(): Unit = {
+ verify(state == UPDATING || state == ABORTED, "Cannot abort after already committed")
+ logInfo(s"Aborting ${version + 1} for $id")
+ rocksDB.rollback()
+ state = ABORTED
+ }
+
+ override def metrics: StateStoreMetrics = {
+ val rocksDBMetrics = rocksDB.metrics
+ def commitLatencyMs(typ: String): Long = rocksDBMetrics.lastCommitLatencyMs.getOrElse(typ, 0L)
+ def avgNativeOpsLatencyMs(typ: String): Long = {
+ rocksDBMetrics.nativeOpsLatencyMicros.get(typ).map(_.avg).getOrElse(0.0).toLong
+ }
+
+ val stateStoreCustomMetrics = Map[StateStoreCustomMetric, Long](
+ CUSTOM_METRIC_SST_FILE_SIZE -> rocksDBMetrics.totalSSTFilesBytes,
+ CUSTOM_METRIC_GET_TIME -> avgNativeOpsLatencyMs("get"),
+ CUSTOM_METRIC_PUT_TIME -> avgNativeOpsLatencyMs("put"),
+ CUSTOM_METRIC_WRITEBATCH_TIME -> commitLatencyMs("writeBatch"),
+ CUSTOM_METRIC_FLUSH_TIME -> commitLatencyMs("flush"),
+ CUSTOM_METRIC_PAUSE_TIME -> commitLatencyMs("pause"),
+ CUSTOM_METRIC_CHECKPOINT_TIME -> commitLatencyMs("checkpoint"),
+ CUSTOM_METRIC_FILESYNC_TIME -> commitLatencyMs("fileSync"),
+ CUSTOM_METRIC_BYTES_COPIED -> rocksDBMetrics.bytesCopied,
+ CUSTOM_METRIC_FILES_COPIED -> rocksDBMetrics.filesCopied,
+ CUSTOM_METRIC_FILES_REUSED -> rocksDBMetrics.filesReused
+ ) ++ rocksDBMetrics.zipFileBytesUncompressed.map(bytes =>
+ Map(CUSTOM_METRIC_ZIP_FILE_BYTES_UNCOMPRESSED -> bytes)).getOrElse(Map())
+
+ StateStoreMetrics(
+ rocksDBMetrics.numUncommittedKeys,
+ rocksDBMetrics.memUsageBytes,
+ stateStoreCustomMetrics)
+ }
+
+ override def hasCommitted: Boolean = state == COMMITTED
+
+ override def toString: String = {
+ s"RocksDBStateStore[id=(op=${id.operatorId},part=${id.partitionId})," +
+ s"dir=${id.storeCheckpointLocation()}]"
+ }
+
+ /** Return the [[RocksDB]] instance in this store. This is exposed mainly for testing. */
+ def dbInstance(): RocksDB = rocksDB
+ }
+
+ override def init(
+ stateStoreId: StateStoreId,
+ keySchema: StructType,
+ valueSchema: StructType,
+ indexOrdinal: Option[Int],
+ storeConf: StateStoreConf,
+ hadoopConf: Configuration): Unit = {
+ this.stateStoreId_ = stateStoreId
+ this.keySchema = keySchema
+ this.valueSchema = valueSchema
+ this.storeConf = storeConf
+ this.hadoopConf = hadoopConf
+ rocksDB // lazy initialization
+ }
+
+ override def stateStoreId: StateStoreId = stateStoreId_
+
+ override def getStore(version: Long): StateStore = {
+ require(version >= 0, "Version cannot be less than 0")
+ rocksDB.load(version)
+ new RocksDBStateStore(version)
+ }
+
+ override def doMaintenance(): Unit = {
+ rocksDB.cleanup()
+ }
+
+ override def close(): Unit = {
+ rocksDB.close()
+ }
+
+ override def supportedCustomMetrics: Seq[StateStoreCustomMetric] = ALL_CUSTOM_METRICS
+
+ private[state] def latestVersion: Long = rocksDB.getLatestVersion()
+
+ /** Internal fields and methods */
+
+ @volatile private var stateStoreId_ : StateStoreId = _
+ @volatile private var keySchema: StructType = _
+ @volatile private var valueSchema: StructType = _
+ @volatile private var storeConf: StateStoreConf = _
+ @volatile private var hadoopConf: Configuration = _
+
+ private[sql] lazy val rocksDB = {
+ val dfsRootDir = stateStoreId.storeCheckpointLocation().toString
+ val storeIdStr = s"StateStoreId(opId=${stateStoreId.operatorId}," +
+ s"partId=${stateStoreId.partitionId},name=${stateStoreId.storeName})"
+ val sparkConf = Option(SparkEnv.get).map(_.conf).getOrElse(new SparkConf)
+ val localRootDir = Utils.createTempDir(Utils.getLocalDir(sparkConf), storeIdStr)
+ new RocksDB(dfsRootDir, RocksDBConf(storeConf), localRootDir, hadoopConf, storeIdStr)
+ }
+
+ private lazy val encoder = new StateEncoder
+
+ private def verify(condition: => Boolean, msg: String): Unit = {
+ if (!condition) { throw new IllegalStateException(msg) }
+ }
+
+ /**
+ * Encodes/decodes UnsafeRows to versioned byte arrays.
+ * It uses the first byte of the generated byte array to store the version the describes how the
Review comment:
Ah yea, thanks!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874643532
Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/45208/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-876358169
**[Test build #140779 has finished](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140779/testReport)** for PR 33187 at commit [`1ac5835`](https://github.com/apache/spark/commit/1ac583553e05f217b224b8c07421da0568b41b25).
* This patch passes all tests.
* This patch merges cleanly.
* This patch adds no public classes.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] viirya commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
viirya commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r665946791
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
Review comment:
I recommend to be consistent with `HDFSBackedStateStore` where it uses `version` and `newVersion`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874981560
**[Test build #140707 has finished](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140707/testReport)** for PR 33187 at commit [`e63a380`](https://github.com/apache/spark/commit/e63a380c599052cabc130ac2d7794689f621323a).
* This patch passes all tests.
* This patch merges cleanly.
* This patch adds no public classes.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874490690
**[Test build #140685 has started](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140685/testReport)** for PR 33187 at commit [`9ddbf58`](https://github.com/apache/spark/commit/9ddbf589035dc9450944ca7c1bc52fd5b419d51d).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins removed a comment on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins removed a comment on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874852703
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder-K8s/45218/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] AmplabJenkins commented on pull request #33187: [WIP][SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
AmplabJenkins commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-872876856
Refer to this link for build results (access rights to CI server needed):
https://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/140577/
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] xuanyuanking commented on a change in pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
xuanyuanking commented on a change in pull request #33187:
URL: https://github.com/apache/spark/pull/33187#discussion_r666021054
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
Review comment:
Actually for RocksDB, we keep the name `newVersion`. The major difference here is we have a `loadedVersion` concept, so that's why for the provider side, we have a `latestVersion`.
##########
File path: sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/RocksDBStateStoreProvider.scala
##########
@@ -0,0 +1,328 @@
+/*
+ * 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.streaming.state
+
+import java.io._
+
+import org.apache.hadoop.conf.Configuration
+
+import org.apache.spark.{SparkConf, SparkEnv}
+import org.apache.spark.internal.Logging
+import org.apache.spark.sql.catalyst.expressions.UnsafeRow
+import org.apache.spark.sql.types.StructType
+import org.apache.spark.unsafe.Platform
+import org.apache.spark.util.Utils
+
+private[state] class RocksDBStateStoreProvider
+ extends StateStoreProvider with Logging with Closeable {
+ import RocksDBStateStoreProvider._
+
+ class RocksDBStateStore(lastVersion: Long) extends StateStore {
+ /** Trait and classes representing the internal state of the store */
+ trait STATE
+ case object UPDATING extends STATE
+ case object COMMITTED extends STATE
+ case object ABORTED extends STATE
+
+ @volatile private var state: STATE = UPDATING
+ @volatile private var isValidated = false
+
+ override def id: StateStoreId = RocksDBStateStoreProvider.this.stateStoreId
+
+ override def version: Long = lastVersion
+
+ override def get(key: UnsafeRow): UnsafeRow = {
+ verify(key != null, "Key cannot be null")
+ val value = encoder.decodeValue(rocksDB.get(encoder.encode(key)))
+ if (!isValidated && value != null) {
+ StateStoreProvider.validateStateRowFormat(
+ key, keySchema, value, valueSchema, storeConf)
+ isValidated = true
+ }
+ value
+ }
+
+ override def put(key: UnsafeRow, value: UnsafeRow): Unit = {
+ verify(state == UPDATING, "Cannot put after already committed or aborted")
+ verify(key != null, "Key cannot be null")
+ require(value != null, "Cannot put a null value")
+ logDebug(s"Storing $key => $value")
Review comment:
Make sense, let me delete this debug log.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org
[GitHub] [spark] SparkQA commented on pull request #33187: [SPARK-35988][SS] The implementation for RocksDBStateStoreProvider
Posted by GitBox <gi...@apache.org>.
SparkQA commented on pull request #33187:
URL: https://github.com/apache/spark/pull/33187#issuecomment-874626176
**[Test build #140685 has finished](https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder/140685/testReport)** for PR 33187 at commit [`9ddbf58`](https://github.com/apache/spark/commit/9ddbf589035dc9450944ca7c1bc52fd5b419d51d).
* This patch **fails Spark unit tests**.
* This patch merges cleanly.
* This patch adds the following public classes _(experimental)_:
* ` class RocksDBStateStore(lastVersion: Long) extends StateStore `
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: reviews-unsubscribe@spark.apache.org
For additional commands, e-mail: reviews-help@spark.apache.org