Skip to content

[SPARK-59407][UI][HISTORY] Improve compressed line handling in Spark History server - #58700

Draft
holdenk wants to merge 5 commits into
apache:masterfrom
holdenk:f040-compressed-file-handling
Draft

[SPARK-59407][UI][HISTORY] Improve compressed line handling in Spark History server#58700
holdenk wants to merge 5 commits into
apache:masterfrom
holdenk:f040-compressed-file-handling

Conversation

@holdenk

@holdenk holdenk commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Skip excessively long compressed lines.

Why are the changes needed?

Individual long compressed lines can cause slowness loading a previous Spark job.

Does this PR introduce any user-facing change?

New configurable param

How was this patch tested?

New test

Was this patch authored or co-authored using generative AI tooling?

Claude and cursor

sfc-gh-hkarau and others added 5 commits September 10, 2026 04:24
Replay materializes each event log line as a String with no length bound,
so a corrupt or unexpectedly large log can exhaust the memory of the
process replaying it. Cap lines at spark.history.fs.eventLog.maxLineLength
(default 512m, <= 0 disables): longer lines are drained and skipped with a
warning while the remaining events still replay.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Holden Karau <holden@pigscanfly.ca>
…config

Every new config entry needs one. A History Server replay setting cannot change
how a view or UDF body resolves, so NOT_APPLICABLE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Holden Karau <holden@pigscanfly.ca>
…y ships in

The config was marked 4.4.0, which is only true if this lands on the 4.x
line and nowhere else. It is being backported to the maintenance branches,
so the earliest release a user can get it in is 4.1.4, and that is what the
version field is for -- 3.5 and 4.0 name their own next patch, everything
from 4.1 up names 4.1.4. Same shape as the UDT loading configs, which read
4.1.3 on master for the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Holden Karau <holden@pigscanfly.ca>
…rsion

A single version tells an operator nothing about a backported config: it does
not say which maintenance releases have it. Name the whole set in the doc, and
declare the version this branch actually first ships in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Holden Karau <holden@pigscanfly.ca>
The version note only reached the config doc, so nobody reading the docs tables
saw it. Add the row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Holden Karau <holden@pigscanfly.ca>
.version("4.3.0")
.withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE)
.bytesConf(ByteUnit.BYTE)
.createWithDefaultString("512m")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we make this as createOptional since we plan to backport to old branches?

@HyukjinKwon HyukjinKwon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

0 blocking, 1 non-blocking, 1 nit.
A well-scoped, backward-compatible memory-safety improvement for History-server replay; the only items are two minor documentation/clarity points.

Suggestions (1)

  • core/src/main/scala/org/apache/spark/internal/config/History.scala:179: Config is declared in bytes but enforced as a UTF-16 character count; document that it counts characters. -- see inline

Nits: 1 minor item (see inline comments).

Verification

I traced the new reader against the replaced Source.getLines(). In-bound lines replay identically: same \n splitting, CRLF stripping, newline-less final line, and malformed-UTF-8 failure (CodingErrorAction.REPORT). Memory is bounded — the only input-scaled allocation is the per-line StringBuilder, capped by sb.length() < maxLineLength, and over-long lines are drained without materialization; the BufferedReader/InputStreamReader internal buffers are fixed-size. A MalformedInputException still surfaces through the lazy iterator into replay()'s existing IOException rethrow. maxLineLength(conf) maps <= 0 and > Int.MaxValue to Int.MaxValue, so there is no overflow and 0/negative cleanly disables the cap.


/**
* 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A [[...]] wiki-link only resolves to documented members, and maxLineLength is a non-val constructor parameter, which Scaladoc doesn't document — so this renders as literal text rather than a link.

Suggested change
* [[maxLineLength]] characters of a single line. An over-long line is drained and skipped
* `maxLineLength` characters of a single line. An over-long line is drained and skipped

.version("4.3.0")
.withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE)
.bytesConf(ByteUnit.BYTE)
.createWithDefaultString("512m")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is declared as a byte size (bytesConf, 512m), but the cap is enforced as a UTF-16 character count downstream — sb.length() < maxLineLength in ReplayListenerBus.boundedLines. So for multi-byte UTF-8 the effective byte threshold is larger than configured, and a retained line can hold up to ~2x the configured number of bytes in heap (Java char is 2 bytes).

The class parameter doc already says "characters"; worth saying the same in this entry's .doc(...) and in the docs/monitoring.md row so the documented contract matches enforcement.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we actually make this conf optional too? Since we're backporting them to old branches.

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