Skip to content

Conversation

@vanzin
Copy link
Contributor

@vanzin vanzin commented Dec 18, 2017

This change adds a new configuration option and support code that limits
how much disk space the SHS will use. The default value is pretty generous
so that applications will, hopefully, only rarely need to be replayed
because of their disk stored being evicted.

This works by keeping track of how much data each application is using.
Also, because it's not possible to know, before replaying, how much space
will be needed, it's possible that usage will exceed the configured limit
temporarily. The code uses the concept of a "lease" to try to limit how
much the SHS will exceed the limit in those cases.

Active UIs are also tracked, so they're never deleted. This works in
tandem with the existing option of how many active UIs are loaded; because
unused UIs will be unloaded, their disk stores will also become candidates
for deletion. If the data is not deleted, though, re-loading the UI is
pretty quick.

…ver.

This change adds a new configuration option and support code that limits
how much disk space the SHS will use. The default value is pretty generous
so that applications will, hopefully, only rarely need to be replayed
because of their disk stored being evicted.

This works by keeping track of how much data each application is using.
Also, because it's not possible to know, before replaying, how much space
will be needed, it's possible that usage will exceed the configured limit
temporarily. The code uses the concept of a "lease" to try to limit how
much the SHS will exceed the limit in those cases.

Active UIs are also tracked, so they're never deleted. This works in
tandem with the existing option of how many active UIs are loaded; because
unused UIs will be unloaded, their disk stores will also become candidates
for deletion. If the data is not deleted, though, re-loading the UI is
pretty quick.
@vanzin
Copy link
Contributor Author

vanzin commented Dec 18, 2017

Some previous comments for this PR can be found at: vanzin#39

@SparkQA
Copy link

SparkQA commented Dec 19, 2017

Test build #85072 has finished for PR 20011 at commit 8b43f4d.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

* event log. By default it's 30% of the event log size.
*/
private def approximateSize(eventLogSize: Long): Long = {
math.ceil(eventLogSizeRatio * eventLogSize).toLong
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll probably need to look at a better heuristic here. This is probably not a good approximation if the logs are compressed, and #20013 also affects this.

Also remove the config to tweak the heuristic since it doesn't make
a lot of sense anymore.
@SparkQA
Copy link

SparkQA commented Dec 19, 2017

Test build #85129 has finished for PR 20011 at commit 31c2755.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented Dec 22, 2017

Test build #85284 has finished for PR 20011 at commit 2d72dd1.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.


// Cap the value at 10% of the max size; this assumes that element cleanup will put a cap on
// how large the disk store can get, which may not always be the case.
math.min(expectedSize, maxUsage / 10)
Copy link
Contributor

Choose a reason for hiding this comment

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

this seems really arbitrary, and could have a ton of error. Do we really need it at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, all of these heuristics can have a lot of error. Increasing this increases the odds that an existing store will be deleted if another log starts to be parsed in parallel.

Don't really feel strongly either way, so away it goes.

* @param listing The listing store, used to persist usage data.
* @param clock Clock instance to use.
*/
private class DiskStoreManager(
Copy link
Contributor

Choose a reason for hiding this comment

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

there is another very different DiskStore, can we name this something else so its easier to tell they're unrelated? KVStoreDiskManager?

private def mockManager(): DiskStoreManager = {
val conf = new SparkConf().set(MAX_LOCAL_DISK_USAGE, MAX_USAGE)
val manager = spy(new DiskStoreManager(conf, testDir, store, new ManualClock()))
doReturn(0L).when(manager).sizeOf(any(classOf[File]))
Copy link
Contributor

Choose a reason for hiding this comment

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

tests pass without this line ... and its kinda strange, I tried removing it because I was wondering why you would want this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just added in case the method was called with an unexpected file. Will remove.

lease3.rollback()
lease4.rollback()
assert(hasFreeSpace(manager, 1))
assert(!hasFreeSpace(manager, 2))
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I don't think hasFreeSpace actually helps at all, here you could replace both of these with assert(manager.free() === 1) and everywhere else do assert(manager.free() === 0) etc.

val dst2 = lease2.commit("app2", None)
assert(!hasFreeSpace(manager, 1))

// Rollback 3 and 4, now there should be 1 left.
Copy link
Contributor

Choose a reason for hiding this comment

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

can you switch the naming scheme to be leaseA, leaseB, etc.? its a bit hard to tell when you're talking about sizes and when you're referring to specific leases.

val lease5 = manager.lease(1)
doReturn(3L).when(manager).sizeOf(meq(lease5.path))
lease5.commit("app2", None)
assert(dst2.exists())
Copy link
Contributor

Choose a reason for hiding this comment

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

would help here to have a comment explaining the case this really represents -- replaying an app because the event log has been updated, so replacing the old kvstore. Also just to make that clear you could add

val dst5 = lease5.commit("app2", None)
assert(dst5 === dst2)

(at first I thought "app2" was a copy/paste mistake here)

// Try a big lease that should cause the committed app to be evicted.
val lease6 = manager.lease(6)
assert(!dst2.exists())
assert(!hasFreeSpace(manager, 1))
Copy link
Contributor

Choose a reason for hiding this comment

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

add a case with getting another lease, even though you're already beyond the size limit (don't think that is covered?)

/** Visible for testing. Return the size of a directory. */
private[history] def sizeOf(path: File): Long = FileUtils.sizeOf(path)

private[history] class Lease(val path: File, private val leased: Long) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can you rename path to tmpPath


active.synchronized {
require(!active.contains(appId -> attemptId),
s"Cannot commit lease for active application $appId / $attemptId")
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the scenario you're worried about here? you could have two threads both trying to call commit, and they might both get past this check, and then clobber each other later on, so this is more limited protection. The idea is just to make sure that you never do a commit while the UI has a store open, as a check of the whole UI cache eviction process?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is more of a sanity check that the situation you describe should not happen. The SHS code should be ensuring that there's only a single thread parsing logs for an application, and that while that happens, the app's UI is not loaded. This just asserts that's the case.

Marcelo Vanzin added 3 commits December 22, 2017 13:27
logInfo(s"Deleting store for ${info.appId}/${info.attemptId}.")
deleteStore(new File(info.path))
updateUsage(-info.size, committed = true)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the change to track committed space. I'm wondering if we should log more here, even at Info level, summarizing the available space (I can see users complaining that they configured it to 10GB but its taking 12GB and wanting an explanation -- would be nice if the logs already had all the relevant info). Maybe something like

val totalNeeded = size
var activeSize = 0
...
if (!isActive) {
  evicted += info
  needed -= info.size
} else {
  activeSize += info.size
}
...
logInfo(s"Deleted ${evicted.size} apps to free ${Utils.bytesToString(needed)})
if (needed > 0) {
  val current = currentUsage.get()
  val leased = Utils.bytesToString(current - committedUsage.get())
  logInfo(s"Did not free requested ${Utils.bytesToString(totalNeeded)}.  Current usage is ${Utils.bytesToString(current).  $leased (estimated) used by apps actively updating their kvstores; ${Utils.bytesToString(active)} used by active applications.")
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I'll add some more logs.

@SparkQA
Copy link

SparkQA commented Dec 23, 2017

Test build #85320 has finished for PR 20011 at commit be3f130.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented Dec 23, 2017

Test build #85326 has finished for PR 20011 at commit 931b2d2.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

Copy link
Contributor

@squito squito left a comment

Choose a reason for hiding this comment

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

lgtm

@asfgit asfgit closed this in 8b49704 Dec 29, 2017
@squito
Copy link
Contributor

squito commented Dec 29, 2017

merged to master

@vanzin vanzin deleted the SPARK-20654 branch January 2, 2018 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants