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

GODRIVER-2572 Add log messages to Server selection spec #1325

Merged
merged 56 commits into from
Aug 17, 2023

Conversation

prestonvasquez
Copy link
Collaborator

@prestonvasquez prestonvasquez commented Jul 18, 2023

GODRIVER-2572

Summary

Define structured log messages for server selection.

Add an explicit test to ensure that no extra server selection events are observed for session support in accordance with DRIVERS-2530.

This PR implements changes required as part of GODRIVER-2466 , specifically recording type-specific data for the expectedSdamEvent, referenced here.

Background & Motivation

@@ -528,38 +549,40 @@ func setClientOptionsFromURIOptions(clientOpts *options.ClientOptions, uriOpts b
var wcSet bool

for key, value := range uriOpts {
switch key {
switch strings.ToLower(key) {
Copy link
Collaborator Author

@prestonvasquez prestonvasquez Jul 26, 2023

Choose a reason for hiding this comment

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

Suggest using case insensitive matching. The motivation for this is that the "failCommand" specifications indicate that the application name field in spec tests should be "appName". However, there doesn't seem to be consistency in the unified spec tests, often "appname" is used, especially in logging. This update will ensure more consistent behavior.

@prestonvasquez prestonvasquez marked this pull request as ready for review July 26, 2023 19:56
@prestonvasquez prestonvasquez requested a review from a team as a code owner July 26, 2023 19:56
@prestonvasquez prestonvasquez requested review from matthewdale, qingyang-hu and blink1073 and removed request for a team July 26, 2023 19:56
internal/logger/component.go Outdated Show resolved Hide resolved
internal/logger/component.go Outdated Show resolved Hide resolved
mongo/integration/unified/event.go Outdated Show resolved Hide resolved
x/mongo/driver/operation.go Outdated Show resolved Hide resolved
mongo/description/server_selector.go Outdated Show resolved Hide resolved
mongo/description/server_selector.go Outdated Show resolved Hide resolved

// String returns the JSON string representation of the serverSelectorInfo.
func (sss serverSelectorInfo) String() string {
bytes, _ := json.Marshal(sss)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm concerned that the nested JSON created here will be difficult to use when output by a structured logger. Is there another format we can use to combine multiple points of info that works better when nested in JSON? Or maybe only produce JSON when the output is unstructured?

Copy link
Collaborator Author

@prestonvasquez prestonvasquez Aug 8, 2023

Choose a reason for hiding this comment

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

Could you elaborate on what you mean by this: "difficult to use when output by a structured logger" ? The output is the type of selector, associated data, and the child selectors (which is only relevant in the composite case). Here is an example:

{
  "Type": "compositeSelector",
  "Selectors": [
    {
      "Type": "writeSelector"
    },
    {
      "Type": "latencySelector"
    }
  ]
}

We could omit child selectors. I would argue that, in the context of logging, more information is better.

Copy link
Collaborator

Choose a reason for hiding this comment

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

My concern is actually that the server selector information is a string containing escaped JSON, rather than a JSON object. Consider the example:

{
    "message": "Server selection started",
    "operation": "find",
    "operationId": 1,
    "selector": "{\"Type\":\"compositeSelector\",\"Selectors\":[{\"Type\":\"readPrefSelector\",\"Data\":\"primary\"},{\"Type\":\"latencySelector\"}]}",
    "timestamp": 1692071928112600000,
    "topologyDescription": "Type: Unknown, Servers: [{ Addr: localhost:27017, Type: Unknown, State: Connected, Average RTT: 0s, Min RTT: 0s }, { Addr: localhost:27018, Type: Unknown, State: Connected, Average RTT: 0s, Min RTT: 0s }, { Addr: localhost:27019, Type: Unknown, State: Connected, Average RTT: 0s, Min RTT: 0s }, ]"
}

That selector information would be more usable and readable as a JSON object.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The specifications require this field to be a string. This representation seems reasonable to me. However, if you want to unescape the string we can build this data directly, similar to what we do with the Server.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Since the spec requires it to be a string, the escaped JSON seems like a reasonable solution.

Comment on lines +642 to +644
if startedInfo.cmdName != op.Name {
op.Name = startedInfo.cmdName
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a way we can prevent this inconsistency from ever happening? Does the command name ever depend on server info, or is it constant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

AFAICT command name is constant and is sourced from the command document when creating a wire message. Technically, these two values should never differ. However, one is sourced from the command function and the other from a field on the operation struct. Since their source differs, it is seems best to make this check.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sounds good.

mongo/collection.go Outdated Show resolved Hide resolved
mongo/description/server_selector.go Outdated Show resolved Hide resolved
Comment on lines 62 to 68
observedEvents map[monitoringEventType]struct{}
storedEvents map[monitoringEventType][]string
eventsCount map[monitoringEventType]int32
serverDescriptionChangedEventsCount map[serverDescriptionChangedEvent]int32

eventsCountLock sync.RWMutex
eventsCountLock sync.RWMutex
serverDescriptionChangedEventsCountLock sync.RWMutex
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 use sync.Map for any of these to simplify the locking logic?

There also seems to be significant overlap in the recorded data. Is it possible to store all of the observed events and then calculate the required counts and other stats on-demand?

Copy link
Collaborator Author

@prestonvasquez prestonvasquez Aug 14, 2023

Choose a reason for hiding this comment

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

The documentation for sync.Map states that it's intended use includes the following:

when the entry for a given key is only ever written once but read many times, as in caches that only grow.

This is not the case for any of these observation sets, as like-keys are written to as many times as an event being measured might occur.

mongo/integration/unified/event.go Outdated Show resolved Hide resolved
x/mongo/driver/topology/topology.go Outdated Show resolved Hide resolved
@github-actions
Copy link

github-actions bot commented Aug 14, 2023

API Change Report

./x/mongo/driver

compatible changes

Operation.Name: added

x/mongo/driver/topology/topology.go Show resolved Hide resolved

// String returns the JSON string representation of the serverSelectorInfo.
func (sss serverSelectorInfo) String() string {
bytes, _ := json.Marshal(sss)
Copy link
Collaborator

Choose a reason for hiding this comment

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

My concern is actually that the server selector information is a string containing escaped JSON, rather than a JSON object. Consider the example:

{
    "message": "Server selection started",
    "operation": "find",
    "operationId": 1,
    "selector": "{\"Type\":\"compositeSelector\",\"Selectors\":[{\"Type\":\"readPrefSelector\",\"Data\":\"primary\"},{\"Type\":\"latencySelector\"}]}",
    "timestamp": 1692071928112600000,
    "topologyDescription": "Type: Unknown, Servers: [{ Addr: localhost:27017, Type: Unknown, State: Connected, Average RTT: 0s, Min RTT: 0s }, { Addr: localhost:27018, Type: Unknown, State: Connected, Average RTT: 0s, Min RTT: 0s }, { Addr: localhost:27019, Type: Unknown, State: Connected, Average RTT: 0s, Min RTT: 0s }, ]"
}

That selector information would be more usable and readable as a JSON object.

Copy link
Collaborator

@matthewdale matthewdale 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 👍


// String returns the JSON string representation of the serverSelectorInfo.
func (sss serverSelectorInfo) String() string {
bytes, _ := json.Marshal(sss)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since the spec requires it to be a string, the escaped JSON seems like a reasonable solution.

Comment on lines +642 to +644
if startedInfo.cmdName != op.Name {
op.Name = startedInfo.cmdName
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sounds good.

@prestonvasquez prestonvasquez merged commit 4bd1c27 into mongodb:master Aug 17, 2023
16 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants