-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-54354][SQL] Fix Spark hanging when there's not enough JVM heap memory for broadcast hashed relation #53065
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 5 commits
5735add
43607e1
bc272e5
7a6ea21
69149ab
2c557ff
416e3a9
7a3d057
063d136
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 |
|---|---|---|
|
|
@@ -143,9 +143,8 @@ private[execution] object HashedRelation { | |
| new TaskMemoryManager( | ||
| new UnifiedMemoryManager( | ||
| new SparkConf().set(MEMORY_OFFHEAP_ENABLED.key, "false"), | ||
| Long.MaxValue, | ||
| Long.MaxValue / 2, | ||
| 1), | ||
| Runtime.getRuntime.maxMemory, | ||
| Runtime.getRuntime.maxMemory / 2, 1), | ||
| 0) | ||
| } | ||
|
|
||
|
|
@@ -401,9 +400,8 @@ private[joins] class UnsafeHashedRelation( | |
| val taskMemoryManager = new TaskMemoryManager( | ||
| new UnifiedMemoryManager( | ||
| new SparkConf().set(MEMORY_OFFHEAP_ENABLED.key, "false"), | ||
| Long.MaxValue, | ||
| Long.MaxValue / 2, | ||
| 1), | ||
| Runtime.getRuntime.maxMemory, | ||
| Runtime.getRuntime.maxMemory / 2, 1), | ||
|
||
| 0) | ||
|
|
||
| val pageSizeBytes = Option(SparkEnv.get).map(_.memoryManager.pageSizeBytes) | ||
|
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. Do you know if the per-JVM memory manager here can be used?
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. Not right now but I think it's the final target. We cannot directly modify the memory manager here, because currently Spark has to stick BHJ's memory allocation on the JVM heap. The per-JVM memory manager may be using off-heap mode. #52817 will improve SHJ to make sure it follows the per-JVM memory manager's memory mode, but we need a separate solution for BHJ in the future (which relies on the |
||
|
|
@@ -576,9 +574,8 @@ private[execution] final class LongToUnsafeRowMap( | |
| new TaskMemoryManager( | ||
| new UnifiedMemoryManager( | ||
| new SparkConf().set(MEMORY_OFFHEAP_ENABLED.key, "false"), | ||
| Long.MaxValue, | ||
| Long.MaxValue / 2, | ||
| 1), | ||
| Runtime.getRuntime.maxMemory, | ||
| Runtime.getRuntime.maxMemory / 2, 1), | ||
|
||
| 0), | ||
| 0) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -27,7 +27,7 @@ import org.apache.spark.SparkConf | |||||||||||||||||||||||||||||
| import org.apache.spark.SparkException | ||||||||||||||||||||||||||||||
| import org.apache.spark.internal.config._ | ||||||||||||||||||||||||||||||
| import org.apache.spark.internal.config.Kryo._ | ||||||||||||||||||||||||||||||
| import org.apache.spark.memory.{TaskMemoryManager, UnifiedMemoryManager} | ||||||||||||||||||||||||||||||
| import org.apache.spark.memory.{SparkOutOfMemoryError, TaskMemoryManager, UnifiedMemoryManager} | ||||||||||||||||||||||||||||||
| import org.apache.spark.serializer.KryoSerializer | ||||||||||||||||||||||||||||||
| import org.apache.spark.sql.catalyst.InternalRow | ||||||||||||||||||||||||||||||
| import org.apache.spark.sql.catalyst.expressions._ | ||||||||||||||||||||||||||||||
|
|
@@ -42,9 +42,8 @@ import org.apache.spark.util.collection.CompactBuffer | |||||||||||||||||||||||||||||
| class HashedRelationSuite extends SharedSparkSession { | ||||||||||||||||||||||||||||||
| val umm = new UnifiedMemoryManager( | ||||||||||||||||||||||||||||||
| new SparkConf().set(MEMORY_OFFHEAP_ENABLED.key, "false"), | ||||||||||||||||||||||||||||||
| Long.MaxValue, | ||||||||||||||||||||||||||||||
| Long.MaxValue / 2, | ||||||||||||||||||||||||||||||
| 1) | ||||||||||||||||||||||||||||||
| Runtime.getRuntime.maxMemory, | ||||||||||||||||||||||||||||||
| Runtime.getRuntime.maxMemory / 2, 1) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| val mm = new TaskMemoryManager(umm, 0) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -753,4 +752,18 @@ class HashedRelationSuite extends SharedSparkSession { | |||||||||||||||||||||||||||||
| map.free() | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| test("UnsafeHashedRelation should throw OOM when there isn't enough memory") { | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| val mm = Option(taskMemoryManager).getOrElse { | |
| new TaskMemoryManager( | |
| new UnifiedMemoryManager( | |
| new SparkConf().set(MEMORY_OFFHEAP_ENABLED.key, "false"), | |
| Long.MaxValue, | |
| Long.MaxValue / 2, | |
| 1), | |
| 0) | |
| } |
Outdated
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.
This is a bad test, and will likely to break the CI process. Can we put it in the PR description as a manual test?
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.
Hi @cloud-fan, thanks for reviewing.
This is a bad test, and will likely to break the CI process.
If you meant the OOM error could break the CI, I think we already rely on the similar logic in the production code:
spark/core/src/main/java/org/apache/spark/memory/TaskMemoryManager.java
Lines 390 to 403 in dce992b
| try { | |
| page = memoryManager.tungstenMemoryAllocator().allocate(acquired); | |
| } catch (OutOfMemoryError e) { | |
| logger.warn("Failed to allocate a page ({} bytes), try again.", | |
| MDC.of(LogKeys.PAGE_SIZE, acquired)); | |
| // there is no enough memory actually, it means the actual free memory is smaller than | |
| // MemoryManager thought, we should keep the acquired memory. | |
| synchronized (this) { | |
| acquiredButNotUsed += acquired; | |
| allocatedPages.clear(pageNumber); | |
| } | |
| // this could trigger spilling to free some pages. | |
| return allocatePage(size, consumer); | |
| } |
Or is there anything else you are concerned about?
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.
It's ok to test with "managed" OOM that is thrown by us, but not a real OOM that destabilize the CI service.
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.
Sounds good to me. Removed and updated the PR description.
Uh oh!
There was an error while loading. Please reload this page.