-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-41054][UI][CORE] Support RocksDB as KVStore in live UI #38567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,17 +17,20 @@ | |
|
|
||
| package org.apache.spark.status | ||
|
|
||
| import java.io.File | ||
| import java.util.{List => JList} | ||
|
|
||
| import scala.collection.JavaConverters._ | ||
| import scala.collection.mutable.HashMap | ||
|
|
||
| import org.apache.spark.{JobExecutionStatus, SparkConf, SparkContext} | ||
| import org.apache.spark.internal.config.History.HybridStoreDiskBackend | ||
| import org.apache.spark.internal.config.Status.LIVE_UI_LOCAL_STORE_DIR | ||
| import org.apache.spark.status.api.v1 | ||
| import org.apache.spark.storage.FallbackStorage.FALLBACK_BLOCK_MANAGER_ID | ||
| import org.apache.spark.ui.scope._ | ||
| import org.apache.spark.util.Utils | ||
| import org.apache.spark.util.kvstore.{InMemoryStore, KVStore} | ||
| import org.apache.spark.util.kvstore.KVStore | ||
|
|
||
| /** | ||
| * A wrapper around a KVStore that provides methods for accessing the API data stored within. | ||
|
|
@@ -769,7 +772,14 @@ private[spark] object AppStatusStore { | |
| def createLiveStore( | ||
| conf: SparkConf, | ||
| appStatusSource: Option[AppStatusSource] = None): AppStatusStore = { | ||
| val store = new ElementTrackingStore(new InMemoryStore(), conf) | ||
| val storePath = conf.get(LIVE_UI_LOCAL_STORE_DIR).map(new File(_)) | ||
| // For the disk-based KV store of live UI, let's simply make it ROCKSDB only for now, | ||
| // instead of supporting both LevelDB and RocksDB. RocksDB is built based on LevelDB with | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not related to this PR, I'm wondering why did we provide these two choices in the first place. RocksDB only seems sufficient.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to the context, I thought Reynold mentioned that the legal issue was resolved. Do you mean that Apache Spark still have legal issue with RocksDB, @gengliangwang ?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dongjoon-hyun I was explaining why @vanzin chose LevelDB. Maybe I was wrong about his reason.
I don't think so. If so, I wouldn't choose it as the only disk backend of live UI.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. Thank you for confirming that. |
||
| // improvements on writes and reads. Furthermore, we can reuse the RocksDBFileManager in | ||
| // streaming for replicating the local RocksDB file to DFS. The replication in DFS can be | ||
| // used for Spark history server. | ||
|
gengliangwang marked this conversation as resolved.
Outdated
|
||
| val kvStore = KVUtils.createKVStore(storePath, HybridStoreDiskBackend.ROCKSDB, conf) | ||
| val store = new ElementTrackingStore(kvStore, conf) | ||
| val listener = new AppStatusListener(store, conf, true, appStatusSource) | ||
| new AppStatusStore(store, listener = Some(listener)) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,16 +18,21 @@ | |
| package org.apache.spark.status | ||
|
|
||
| import java.io.File | ||
| import java.nio.file.Files | ||
|
|
||
| import scala.annotation.meta.getter | ||
| import scala.collection.JavaConverters._ | ||
| import scala.reflect.{classTag, ClassTag} | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonInclude | ||
| import com.fasterxml.jackson.module.scala.DefaultScalaModule | ||
| import org.fusesource.leveldbjni.internal.NativeDB | ||
| import org.rocksdb.RocksDBException | ||
|
|
||
| import org.apache.spark.SparkConf | ||
| import org.apache.spark.deploy.history.{FsHistoryProvider, FsHistoryProviderMetadata} | ||
| import org.apache.spark.internal.Logging | ||
| import org.apache.spark.internal.config.History | ||
| import org.apache.spark.internal.config.History.HYBRID_STORE_DISK_BACKEND | ||
| import org.apache.spark.internal.config.History.HybridStoreDiskBackend | ||
| import org.apache.spark.internal.config.History.HybridStoreDiskBackend._ | ||
|
|
@@ -62,10 +67,14 @@ private[spark] object KVUtils extends Logging { | |
| * the store's metadata. | ||
| * @param conf SparkConf use to get `HYBRID_STORE_DISK_BACKEND` | ||
| */ | ||
| def open[M: ClassTag](path: File, metadata: M, conf: SparkConf): KVStore = { | ||
| def open[M: ClassTag]( | ||
| path: File, | ||
| metadata: M, | ||
| conf: SparkConf, | ||
| diskBackend: Option[HybridStoreDiskBackend.Value] = None): KVStore = { | ||
| require(metadata != null, "Metadata is required.") | ||
|
|
||
| val db = backend(conf) match { | ||
| val db = diskBackend.getOrElse(backend(conf)) match { | ||
| case LEVELDB => new LevelDB(path, new KVStoreScalaSerializer()) | ||
| case ROCKSDB => new RocksDB(path, new KVStoreScalaSerializer()) | ||
| } | ||
|
|
@@ -80,6 +89,44 @@ private[spark] object KVUtils extends Logging { | |
| db | ||
| } | ||
|
|
||
| def createKVStore( | ||
| storePath: Option[File], | ||
| diskBackend: HybridStoreDiskBackend.Value, | ||
| conf: SparkConf): KVStore = { | ||
| storePath.map { path => | ||
| val dir = diskBackend match { | ||
| case LEVELDB => "listing.ldb" | ||
| case ROCKSDB => "listing.rdb" | ||
| } | ||
|
|
||
| val dbPath = Files.createDirectories(new File(path, dir).toPath()).toFile() | ||
| Utils.chmod700(dbPath) | ||
|
|
||
|
|
||
|
tgravescs marked this conversation as resolved.
Outdated
|
||
| val metadata = FsHistoryProviderMetadata( | ||
| FsHistoryProvider.CURRENT_LISTING_VERSION, | ||
| AppStatusStore.CURRENT_VERSION, | ||
| conf.get(History.HISTORY_LOG_DIR)) | ||
|
|
||
| try { | ||
| open(dbPath, metadata, conf, Some(diskBackend)) | ||
| } catch { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For Live UI, are these two exception scenarios possible?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. The LevelDB exception is not possible either. There are only two error-handling branches, and seems too much to distinguish whether the caller is for live UI or SHS.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If unexpected data cleaning doesn't occur in the live UI scenario, I think it is OK
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If something goes wrong and the kv store can't be created for live ui then the entire application fails?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tgravescs If there is exception from RocksDB, it will delete the target directory and open the KV store again. Otherwise if the error is not from RocksDB, it will fall back to the in-memory store. |
||
| // If there's an error, remove the listing database and any existing UI database | ||
| // from the store directory, since it's extremely likely that they'll all contain | ||
| // incompatible information. | ||
| case _: UnsupportedStoreVersionException | _: MetadataMismatchException => | ||
| logInfo("Detected incompatible DB versions, deleting...") | ||
| path.listFiles().foreach(Utils.deleteRecursively) | ||
| open(dbPath, metadata, conf, Some(diskBackend)) | ||
| case dbExc @ (_: NativeDB.DBException | _: RocksDBException) => | ||
| // Get rid of the corrupted data and re-create it. | ||
| logWarning(s"Failed to load disk store $dbPath :", dbExc) | ||
| Utils.deleteRecursively(dbPath) | ||
| open(dbPath, metadata, conf, Some(diskBackend)) | ||
| } | ||
| }.getOrElse(new InMemoryStore()) | ||
| } | ||
|
|
||
| /** Turns a KVStoreView into a Scala sequence, applying a filter. */ | ||
| def viewToSeq[T]( | ||
| view: KVStoreView[T], | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.