-
Notifications
You must be signed in to change notification settings - Fork 29.4k
[SPARK-59407][UI][HISTORY] Improve compressed line handling in Spark History server #58700
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
base: master
Are you sure you want to change the base?
Changes from all commits
571a27a
b01abf7
4aa2fcc
02eb343
c9b6cb1
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 |
|---|---|---|
|
|
@@ -165,6 +165,19 @@ private[spark] object History { | |
| .bytesConf(ByteUnit.BYTE) | ||
| .createWithDefaultString("1m") | ||
|
|
||
| val EVENT_LOG_MAX_LINE_LENGTH = | ||
| ConfigBuilder("spark.history.fs.eventLog.maxLineLength") | ||
| .doc("Maximum length of a single event log line during replay. Lines longer than " + | ||
| "this are skipped with a warning instead of being read into memory, bounding the " + | ||
| "memory replay can use when an event log is corrupt or unexpectedly large. Setting " + | ||
| "this to 0 or a negative value disables the limit. " + | ||
| "Introduced in 4.3.0; also available in 3.5.10, 4.0.5, 4.1.4 and 4.2.1; and in " + | ||
| "all versions after 4.3.0.") | ||
| .version("4.3.0") | ||
| .withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE) | ||
| .bytesConf(ByteUnit.BYTE) | ||
| .createWithDefaultString("512m") | ||
|
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. This is declared as a byte size ( The class parameter doc already says "characters"; worth saying the same in this entry's
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. Can we actually make this conf optional too? Since we're backporting them to old branches. |
||
|
|
||
| private[spark] val EVENT_LOG_ROLLING_MAX_FILES_TO_RETAIN = | ||
| ConfigBuilder("spark.history.fs.eventLog.rolling.maxFilesToRetain") | ||
| .doc("The maximum number of event log files which will be retained as non-compacted. " + | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,22 +17,32 @@ | |||||
|
|
||||||
| package org.apache.spark.scheduler | ||||||
|
|
||||||
| import java.io.{EOFException, InputStream, IOException} | ||||||
| import java.io.{BufferedReader, EOFException, InputStream, InputStreamReader, IOException} | ||||||
| import java.nio.charset.{CodingErrorAction, StandardCharsets} | ||||||
|
|
||||||
| import scala.io.{Codec, Source} | ||||||
| import scala.annotation.tailrec | ||||||
|
|
||||||
| import com.fasterxml.jackson.core.JsonParseException | ||||||
| import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException | ||||||
|
|
||||||
| import org.apache.spark.SparkConf | ||||||
| import org.apache.spark.internal.Logging | ||||||
| import org.apache.spark.internal.LogKeys._ | ||||||
| import org.apache.spark.internal.config.History | ||||||
| import org.apache.spark.scheduler.ReplayListenerBus._ | ||||||
| import org.apache.spark.util.JsonProtocol | ||||||
|
|
||||||
| /** | ||||||
| * A SparkListenerBus that can be used to replay events from serialized event data. | ||||||
| * | ||||||
| * @param maxLineLength Maximum number of characters of a single event log line that will be | ||||||
| * materialized during replay. Longer lines are drained, skipped and | ||||||
| * logged, bounding the memory replay can use when an event log is | ||||||
| * corrupt or unexpectedly large. | ||||||
| */ | ||||||
| private[spark] class ReplayListenerBus extends SparkListenerBus with Logging { | ||||||
| private[spark] class ReplayListenerBus( | ||||||
| maxLineLength: Int = ReplayListenerBus.DEFAULT_MAX_LINE_LENGTH) | ||||||
| extends SparkListenerBus with Logging { | ||||||
|
|
||||||
| /** | ||||||
| * Replay each event in the order maintained in the given stream. The stream is expected to | ||||||
|
|
@@ -56,10 +66,79 @@ private[spark] class ReplayListenerBus extends SparkListenerBus with Logging { | |||||
| sourceName: String, | ||||||
| maybeTruncated: Boolean = false, | ||||||
| eventsFilter: ReplayEventsFilter = SELECT_ALL_FILTER): Boolean = { | ||||||
| val lines = Source.fromInputStream(logData)(Codec.UTF8).getLines() | ||||||
| val lines = boundedLines(logData, sourceName) | ||||||
| replay(lines, sourceName, maybeTruncated, eventsFilter) | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Reads '\n'-terminated lines like Source.getLines(), but never materializes more than | ||||||
| * [[maxLineLength]] characters of a single line. An over-long line is drained and skipped | ||||||
|
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. A
Suggested change
|
||||||
| * with a warning instead of being turned into a String. | ||||||
| */ | ||||||
| private def boundedLines(logData: InputStream, sourceName: String): Iterator[String] = { | ||||||
| // Fail on malformed input like Source.getLines() does instead of replacing it. | ||||||
| val decoder = StandardCharsets.UTF_8.newDecoder() | ||||||
| .onMalformedInput(CodingErrorAction.REPORT) | ||||||
| .onUnmappableCharacter(CodingErrorAction.REPORT) | ||||||
| val reader = new BufferedReader(new InputStreamReader(logData, decoder)) | ||||||
| new Iterator[String] { | ||||||
| private var nextLine: String = _ | ||||||
| private var lineFetched = false | ||||||
| private var warned = false | ||||||
|
|
||||||
| override def hasNext: Boolean = { | ||||||
| if (!lineFetched) { | ||||||
| nextLine = fetchLine() | ||||||
| lineFetched = true | ||||||
| } | ||||||
| nextLine != null | ||||||
| } | ||||||
|
|
||||||
| override def next(): String = { | ||||||
| if (!hasNext) { | ||||||
| throw new NoSuchElementException("No more lines") | ||||||
| } | ||||||
| val line = nextLine | ||||||
| nextLine = null | ||||||
| lineFetched = false | ||||||
| line | ||||||
| } | ||||||
|
|
||||||
| @tailrec private def fetchLine(): String = { | ||||||
| val sb = new java.lang.StringBuilder() | ||||||
| var overLong = false | ||||||
| var c = reader.read() | ||||||
| if (c == -1) { | ||||||
| null | ||||||
| } else { | ||||||
| while (c != -1 && c != '\n') { | ||||||
| if (sb.length() < maxLineLength) { | ||||||
| sb.append(c.toChar) | ||||||
| } else { | ||||||
| overLong = true | ||||||
| } | ||||||
| c = reader.read() | ||||||
| } | ||||||
| if (overLong) { | ||||||
| if (!warned) { | ||||||
| logWarning(log"Skipped event log lines longer than " + | ||||||
| log"${MDC(MAX_SIZE, maxLineLength)} characters in " + | ||||||
| log"${MDC(FILE_NAME, sourceName)}") | ||||||
| warned = true | ||||||
| } | ||||||
| fetchLine() | ||||||
| } else { | ||||||
| // Handle CRLF line endings like Source.getLines() does. | ||||||
| if (sb.length() > 0 && sb.charAt(sb.length() - 1) == '\r') { | ||||||
| sb.setLength(sb.length() - 1) | ||||||
| } | ||||||
| sb.toString | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Overloaded variant of [[replay()]] which accepts an iterator of lines instead of an | ||||||
| * [[InputStream]]. Exposed for use by custom ApplicationHistoryProvider implementations. | ||||||
|
|
@@ -146,6 +225,19 @@ private[spark] class HaltReplayException extends RuntimeException | |||||
|
|
||||||
| private[spark] object ReplayListenerBus { | ||||||
|
|
||||||
| /** | ||||||
| * Default per-line cap during replay: far above any legitimate event line, so replay | ||||||
| * memory stays bounded even for corrupt logs. Matches the default of | ||||||
| * spark.history.fs.eventLog.maxLineLength. | ||||||
| */ | ||||||
| val DEFAULT_MAX_LINE_LENGTH: Int = 512 * 1024 * 1024 | ||||||
|
|
||||||
| /** Resolves the replay line-length cap from configuration; <= 0 disables the cap. */ | ||||||
| def maxLineLength(conf: SparkConf): Int = { | ||||||
| val configured = conf.get(History.EVENT_LOG_MAX_LINE_LENGTH) | ||||||
| if (configured <= 0 || configured > Int.MaxValue) Int.MaxValue else configured.toInt | ||||||
| } | ||||||
|
|
||||||
| type ReplayEventsFilter = (String) => Boolean | ||||||
|
|
||||||
| // utility filter that selects all event logs during replay | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this as createOptional since we plan to backport to old branches?