Skip to content
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

Fix from_json function failure when input contains empty or null strings #8526

Merged

Conversation

cindyyuanjiang
Copy link
Collaborator

@cindyyuanjiang cindyyuanjiang commented Jun 7, 2023

Fixes #8522.

During testing, we found from_json function throws a java.lang.ArrayIndexOutOfBoundsException (from ai.rapids.cudf.TableWithMeta.releaseTable) when the input column contains empty or null strings. When pre-processing the input columns, we have replaced all empty string or null rows in the input column with "{}", but calling ai.rapids.cudf.Table.readJSON on the input column (containing other valid json strings) would result in tables with different number of columns, resulting in future ArrayIndexOutOfBoundsException since rows contain tables of different sizes.
=> To fix this issue, instead of replacing empty strings/nulls with "{}", we replace them with a dummy json string based on the input schema. For example, if schema is StructType([StructField("a", StringType), StructField("b", IntegerType)]), then we will replace empty strings and nulls with "{"a": "", "b": 0}". This ensures that all rows contain tables with the same number of columns after readJSON.

When testing the above solution, we encountered another issue with strip function: rapidsai/cudf#13529. The problem only occurs when the input column contains only empty strings and nulls. Our approach is to first check the data buffer for the input string column, and if it is empty, we do not call strip and just call incRefCount on the origin column.

Follow-up issue tracked: #8542

@sameerz sameerz added the bug Something isn't working label Jun 7, 2023
@cindyyuanjiang cindyyuanjiang self-assigned this Jun 7, 2023
@cindyyuanjiang
Copy link
Collaborator Author

build

val res = struct.fields.foldRight ("") ((field, acc) =>
field.dataType match {
case IntegerType => "\"" + field.name + "\": 0, " + acc
case _ => "\"" + field.name + "\": \"\", " + acc
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to take care of a nested StructType here ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a really inefficient fix in terms of memory usage on the GPU. I fixed most of this in CUDF with rapidsai/cudf#13477 except for the one use case that you are testing in the python code.

Can we have a follow on issue to only do this if all of the strings are empty strings or '{}' in 23.08? We technically only need one row to have this in it and the rest can be empty, but trying to make that happen feels more expensive than just expanding this out everywhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@firestarman Yes, thank you! I will update this to take care of nested StructType.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@revans2 I will file a follow up issue in 23.08. Thank you for pointing this out!

integration_tests/src/main/python/json_test.py Outdated Show resolved Hide resolved
val res = struct.fields.foldRight ("") ((field, acc) =>
field.dataType match {
case IntegerType => "\"" + field.name + "\": 0, " + acc
case _ => "\"" + field.name + "\": \"\", " + acc
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a really inefficient fix in terms of memory usage on the GPU. I fixed most of this in CUDF with rapidsai/cudf#13477 except for the one use case that you are testing in the python code.

Can we have a follow on issue to only do this if all of the strings are empty strings or '{}' in 23.08? We technically only need one row to have this in it and the rest can be empty, but trying to make that happen feels more expensive than just expanding this out everywhere.

input.incRefCount
} else {
withResource(cudf.Scalar.fromString(" ")) { space =>
input.strip(space)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we look for other places that strip, rstrip, and lstrip are called? I did a quick look and I think we might get the same problem on


GpuColumnVector.from(column.getBase.rstrip(t), dataType)

GpuColumnVector.from(column.getBase.lstrip(t), dataType)

But there may be others.

At a minimum we need to test these too and fix them if they are also broken

Copy link
Collaborator Author

@cindyyuanjiang cindyyuanjiang Jun 9, 2023

Choose a reason for hiding this comment

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

@revans2 Thanks! I tested GpuStringTrim[Left, Right] implementation above. The same problem comes up. I added integration tests and fixes for them.


I didn't find tests for the above rstrip() call site, and haven't added a fix yet.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that is fine. That is specifically for a special case ORC char type. We should file an issue for it just so it is not missed, but it should be really rare.

@cindyyuanjiang
Copy link
Collaborator Author

cindyyuanjiang commented Jun 9, 2023

Added fix and tests for the following strip() call site:

GpuColumnVector.from(column.getBase.strip(t), dataType)

Signed-off-by: Cindy Jiang <[email protected]>
@cindyyuanjiang
Copy link
Collaborator Author

build

gerashegalov
gerashegalov previously approved these changes Jun 9, 2023
integration_tests/src/main/python/json_test.py Outdated Show resolved Hide resolved
@cindyyuanjiang
Copy link
Collaborator Author

build

@cindyyuanjiang cindyyuanjiang merged commit 8f967e2 into NVIDIA:branch-23.06 Jun 9, 2023
@cindyyuanjiang cindyyuanjiang deleted the fix-gpu-from-json-func branch June 9, 2023 05:04
case struct: StructType if (struct.fields.length > 0) => {
val jsonFields: Array[String] = struct.fields.map { field =>
field.dataType match {
case IntegerType => s""""${field.name}": 0"""
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, does Spark really return a 0 and not a null for an empty int column???

What happens if a field name has odd characters in it? Can it have a " for example?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am not certain about Spark side. I will investigate more. For odd characters, I think it would fail at readJSON parsing step. I will follow up with this in #8542. Thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] from_json function failed testing with input column containing empty or null string
5 participants