-
Notifications
You must be signed in to change notification settings - Fork 28.9k
[SPARK-24809] [SQL] Serializing LongToUnsafeRowMap in executor may result in data error #21772
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
Conversation
…sult in data error
|
ok to test |
|
@liutang123 can you explain why we are losing data when serializing to disk. Also can you add a unit test? |
|
Test build #93112 has finished for PR 21772 at commit
|
| writeLong(used) | ||
| val cursorFlag = cursor - Platform.LONG_ARRAY_OFFSET | ||
| writeLong(cursorFlag) | ||
| val used = (cursorFlag / 8).toInt |
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.
Is this said, that when (cursor - Platform.LONG_ARRAY_OFFSET) / 8 is over the range of Int, we will have overflow? But later you still do toInt and use the value?
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.
losing data when serializing LongHashedRelation in executor, can you see this picture? In executor, the cursor is 0.
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 you post the image in this PR? The web site you refer contains too many ads.
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.
|
@hvanhovell Thanks for reviewing. Losing data because the variable cursor in executor is |
|
Let me clarify it. So this means that when Is this what you mean? |
|
@viirya Yes, absolutely right. :) |
|
Test build #93267 has finished for PR 21772 at commit
|
|
Jenkins, test this please |
|
Test build #93314 has finished for PR 21772 at commit
|
|
@viirya Hi, Could you have more time to review this PR? |
|
|
||
| val usedWordsNumber = ((cursor - Platform.LONG_ARRAY_OFFSET) / 8).toInt | ||
| writeLong(usedWordsNumber) | ||
| writeLongArray(writeBuffer, page, usedWordsNumber) |
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.
If no good reason, shall we revert this change? Looks like you only rename it?
| val usedWordsNumber = readLong().toInt | ||
| // Set cursor because cursor is used in write function. | ||
| cursor = usedWordsNumber * 8 + Platform.LONG_ARRAY_OFFSET | ||
| page = readLongArray(readBuffer, usedWordsNumber) |
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.
ditto. Can you just update cursor and revert other unrelated change?
| map.free() | ||
| } | ||
|
|
||
| test("SPARK-24809: Serializing LongHashedRelation in executor may result in data error") { |
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.
Is it possible to have an end-to-end test for this?
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.
I think this UT can cover the case I had met.
End-to-end test is too hard to structure because this case just occurs when executor's memory is not enough to hold the block and the broadcast cache is removed by the garbage collector.
|
@liutang123 Thanks for this work. I'm curious that if this is an actual problem you hit in real application, or you just think it is problematic? |
|
@viirya This case occurred in our cluster and we took a lot of time to find this bug. |
| array = readLongArray(readBuffer, length) | ||
| val pageLength = readLong().toInt | ||
| page = readLongArray(readBuffer, pageLength) | ||
| // Set cursor because cursor is used in write function. |
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.
maybe: Restore cursor variable to make this map able to be serialized again on executors?
|
As you actually modify |
| val value1 = new Random().nextLong() | ||
|
|
||
| val key2 = 2L | ||
| val value2 = new Random().nextLong() |
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.
Is it necessary to use Random here? Can we use two arbitrary long values?
|
|
||
| val resultRow = new UnsafeRow(1) | ||
| assert(originalMap.getValue(key1, resultRow).getLong(0) === value1) | ||
| assert(originalMap.getValue(key2, resultRow).getLong(0) === value2) |
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.
We don't need to test LongToUnsafeRowMap's normal feature here. We just need to verify the map after two ser/de can work normally.
| val ser = new KryoSerializer( | ||
| (new SparkConf).set("spark.kryo.referenceTracking", "false")).newInstance() | ||
|
|
||
| val mapSerializedInDriver = ser.deserialize[LongToUnsafeRowMap](ser.serialize(originalMap)) |
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.
nit:
// Simulate serialize/deserialize twice on driver and executor
val firstTimeSerialized = ...
val secondTimeSerialized = ...|
cc @cloud-fan |
|
Test build #93473 has finished for PR 21772 at commit
|
|
Test build #93480 has finished for PR 21772 at commit
|
|
Jenkins, test this please |
|
retest this please |
|
Test build #93516 has finished for PR 21772 at commit
|
| originalMap.append(key2, unsafeProj(InternalRow(value2))) | ||
| originalMap.optimize() | ||
|
|
||
| val ser = new KryoSerializer( |
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.
we can write sparkContext.env.serializer.newInstance()
|
good catch! LGTM |
|
LGTM too. |
|
Test build #93749 has finished for PR 21772 at commit
|
gatorsmile
left a comment
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.
Thanks! Merged to master/2.3/2.2/2.1
…ult in data error When join key is long or int in broadcast join, Spark will use `LongToUnsafeRowMap` to store key-values of the table witch will be broadcasted. But, when `LongToUnsafeRowMap` is broadcasted to executors, and it is too big to hold in memory, it will be stored in disk. At that time, because `write` uses a variable `cursor` to determine how many bytes in `page` of `LongToUnsafeRowMap` will be write out and the `cursor` was not restore when deserializing, executor will write out nothing from page into disk. ## What changes were proposed in this pull request? Restore cursor value when deserializing. Author: liulijia <[email protected]> Closes #21772 from liutang123/SPARK-24809. (cherry picked from commit 2c54aae) Signed-off-by: Xiao Li <[email protected]>
…ult in data error When join key is long or int in broadcast join, Spark will use `LongToUnsafeRowMap` to store key-values of the table witch will be broadcasted. But, when `LongToUnsafeRowMap` is broadcasted to executors, and it is too big to hold in memory, it will be stored in disk. At that time, because `write` uses a variable `cursor` to determine how many bytes in `page` of `LongToUnsafeRowMap` will be write out and the `cursor` was not restore when deserializing, executor will write out nothing from page into disk. ## What changes were proposed in this pull request? Restore cursor value when deserializing. Author: liulijia <[email protected]> Closes #21772 from liutang123/SPARK-24809. (cherry picked from commit 2c54aae) Signed-off-by: Xiao Li <[email protected]>
…ult in data error When join key is long or int in broadcast join, Spark will use `LongToUnsafeRowMap` to store key-values of the table witch will be broadcasted. But, when `LongToUnsafeRowMap` is broadcasted to executors, and it is too big to hold in memory, it will be stored in disk. At that time, because `write` uses a variable `cursor` to determine how many bytes in `page` of `LongToUnsafeRowMap` will be write out and the `cursor` was not restore when deserializing, executor will write out nothing from page into disk. ## What changes were proposed in this pull request? Restore cursor value when deserializing. Author: liulijia <[email protected]> Closes #21772 from liutang123/SPARK-24809. (cherry picked from commit 2c54aae) Signed-off-by: Xiao Li <[email protected]>
…ult in data error When join key is long or int in broadcast join, Spark will use `LongToUnsafeRowMap` to store key-values of the table witch will be broadcasted. But, when `LongToUnsafeRowMap` is broadcasted to executors, and it is too big to hold in memory, it will be stored in disk. At that time, because `write` uses a variable `cursor` to determine how many bytes in `page` of `LongToUnsafeRowMap` will be write out and the `cursor` was not restore when deserializing, executor will write out nothing from page into disk. ## What changes were proposed in this pull request? Restore cursor value when deserializing. Author: liulijia <[email protected]> Closes apache#21772 from liutang123/SPARK-24809.
…ult in data error When join key is long or int in broadcast join, Spark will use `LongToUnsafeRowMap` to store key-values of the table witch will be broadcasted. But, when `LongToUnsafeRowMap` is broadcasted to executors, and it is too big to hold in memory, it will be stored in disk. At that time, because `write` uses a variable `cursor` to determine how many bytes in `page` of `LongToUnsafeRowMap` will be write out and the `cursor` was not restore when deserializing, executor will write out nothing from page into disk. ## What changes were proposed in this pull request? Restore cursor value when deserializing. Author: liulijia <[email protected]> Closes apache#21772 from liutang123/SPARK-24809. (cherry picked from commit 2c54aae) Signed-off-by: Xiao Li <[email protected]>
…ult in data error When join key is long or int in broadcast join, Spark will use `LongToUnsafeRowMap` to store key-values of the table witch will be broadcasted. But, when `LongToUnsafeRowMap` is broadcasted to executors, and it is too big to hold in memory, it will be stored in disk. At that time, because `write` uses a variable `cursor` to determine how many bytes in `page` of `LongToUnsafeRowMap` will be write out and the `cursor` was not restore when deserializing, executor will write out nothing from page into disk. ## What changes were proposed in this pull request? Restore cursor value when deserializing. Author: liulijia <[email protected]> Closes apache#21772 from liutang123/SPARK-24809. (cherry picked from commit 2c54aae) Signed-off-by: Xiao Li <[email protected]>
…ult in data error When join key is long or int in broadcast join, Spark will use `LongToUnsafeRowMap` to store key-values of the table witch will be broadcasted. But, when `LongToUnsafeRowMap` is broadcasted to executors, and it is too big to hold in memory, it will be stored in disk. At that time, because `write` uses a variable `cursor` to determine how many bytes in `page` of `LongToUnsafeRowMap` will be write out and the `cursor` was not restore when deserializing, executor will write out nothing from page into disk. ## What changes were proposed in this pull request? Restore cursor value when deserializing. Author: liulijia <[email protected]> Closes apache#21772 from liutang123/SPARK-24809.
When join key is long or int in broadcast join, Spark will use
LongToUnsafeRowMapto store key-values of the table witch will be broadcasted. But, whenLongToUnsafeRowMapis broadcasted to executors, and it is too big to hold in memory, it will be stored in disk. At that time, becausewriteuses a variablecursorto determine how many bytes inpageofLongToUnsafeRowMapwill be write out and thecursorwas not restore when deserializing, executor will write out nothing from page into disk.What changes were proposed in this pull request?
Restore cursor value when deserializing.