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

JSON Serialization: Change Libraries #3225

Merged
merged 12 commits into from
Oct 19, 2023

Conversation

bsardo
Copy link
Collaborator

@bsardo bsardo commented Oct 16, 2023

This PR attempts to improve serialization performance by swapping out the standard library encoding/json with the json-iterator library.

General notes about json-iterator:

  • the library claims 100% compatibility but falls short of this claim
  • most standard library public functions are provided with the same signature
  • for now, all that we care about are the marshal and unmarshal functions which have the same signature as the standard library

Notable differences between the standard library and json-iterator marshal/unmarshal functions:

  • json-iterator functions always return error strings instead of unmarshal, syntax and marshal error types
  • json-iterator functions do not validate json.RawMessage fields
  • when unmarshaling bytes to a data type containing a json.RawMessage field, if the field in the bytes has the value "null", the four byte value "null" is not written to the json.RawMessage field like it is in the standard library, but rather the value is considered nil and as a result the json.RawMessage field will be empty
  • when marshaling, if one of the fields being marshaled is of type json.RawMessage, it is not validated. Syntax errors are not reported like they are with the standard library.

Implementation:

  • Added the following jsonutil helper function that wrap json-iterator:
    • UnmarshalValid (which unmarshals and performs validation)
    • Unmarshal (which unmarshals without performing validation)
    • Marshal (which marshals without performing validation)
  • Updated the AMP and auction flows to call the helper functions instead of making calls to the standard library Unmarshal and Marshal functions
  • Unmarshaling with validation is only performed when PBS core does not have control over the data source (e.g. a stored request has been loaded from a database, a module has manipulated the data by applying changesets).. and in tests.
  • json-iterator returns untyped string error messages with non-optional "context" for the parse error. We've tried our best to reverse their string formatting to extract a more reasonable error message. Expect this to be improved / fine tuned over time.

expectedErrMessage: "invalid character",
},
{
description: "Valid extension, empty extBidPrebid and invalid imp ext info",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did this test go away?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This test was removed because the json-iterator Marshal function does not validate json.RawMessage fields like the standard library Marshal function so an error in imp.ext will go undetected. In that case I figured we should ensure that whatever is passed into this function is already known to be valid.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good. These were attempts to test the error handling of Marshal, which may very well be a fools errand.

@@ -403,22 +403,6 @@ func TestCreateSanitizedImpExt(t *testing.T) {
},
expectedError: "",
},
{
description: "Marshal Error - imp.ext.prebid",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did this test case go away?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Same reason as above (#3225 (comment))

@hhhjort
Copy link
Collaborator

hhhjort commented Oct 17, 2023

It looks good except I am unclear why 2 test cases went away. Also it seems we did not entirely do away with encoding/json

@bsardo
Copy link
Collaborator Author

bsardo commented Oct 17, 2023

It looks good except I am unclear why 2 test cases went away. Also it seems we did not entirely do away with encoding/json

Yeah I thought we just wanted to focus on updating the main auction flow for now but an update will be made to account for the other endpoints. The test utils should also be updated and perhaps we can reference json.RawMessage through json-iterator library so encoding/json can be completely removed.

@SyntaxNode
Copy link
Contributor

^ These commits extend usage to all other parts of core code and tests. Added error message parsing to attempt to normalize the crazy format of json-iter. Don't want to send this stuff to publishers in a response.

"GBP":0.7662523901
},
"GBP":0.4815162342
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The behavior of json and json-iter match here. The test was incorrectly setup. It failed because of the trailing comma, nothing to do with the repeated elements. Fixed the test and manually verified behavior matches.

},
}

for _, tc := range testCases {
// Execute:
updatedRates := Rates{}
err := json.Unmarshal([]byte(tc.ratesJSON), &updatedRates)
err := jsonutil.UnmarshalValid([]byte(tc.ratesJSON), &updatedRates)
Copy link
Contributor

Choose a reason for hiding this comment

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

Updated all tests to use UnmarshalValid because it seemed like we want to validate in tests. Does that check out?


// Verify:
assert.Equal(t, err != nil, tc.expectsError, tc.desc)
if tc.expectsError {
assert.Equal(t, err.Error(), tc.expectedError.Error(), tc.desc)
assert.Equal(t, tc.expectedError.Error(), err.Error(), tc.desc)
Copy link
Contributor

Choose a reason for hiding this comment

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

Expected and actual orders were reversed, leading to confusing error messages. Fixed.

expectedErrMessage: "invalid character",
},
{
description: "Valid extension, empty extBidPrebid and invalid imp ext info",
Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good. These were attempts to test the error handling of Marshal, which may very well be a fools errand.


msgStartIndex := strings.Index(msg, ": ")
if msgStartIndex == -1 {
return msg
Copy link
Contributor

Choose a reason for hiding this comment

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

Fallback to message as-is if markers aren't found.

hhhjort
hhhjort previously approved these changes Oct 18, 2023
Copy link
Collaborator

@hhhjort hhhjort left a comment

Choose a reason for hiding this comment

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

Looks good once the merge conflict is resolved

@bsardo
Copy link
Collaborator Author

bsardo commented Oct 18, 2023

LGTM

Copy link
Collaborator Author

@bsardo bsardo left a comment

Choose a reason for hiding this comment

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

LGTM

@SyntaxNode SyntaxNode changed the title JSON Serialization: Use Different Library in Main Auction Flow JSON Serialization: Change Libraries Oct 19, 2023
@bsardo bsardo merged commit 6f630fd into prebid:master Oct 19, 2023
5 checks passed
svamiftah pushed a commit to sovrn/prebid-server that referenced this pull request Nov 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants