-
Notifications
You must be signed in to change notification settings - Fork 2k
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
feat: Automatic schema reporting to Apollo Graph Manager #4084
Conversation
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.
The description here needs to be more detailed; this PR is doing 2 things, using the new schemaId field in metrics and adding support for schema reporting.
Had to stop mid-review, will come back after iteration..
94eaeb5
to
b3ca921
Compare
7a4e6ef
to
30c11bc
Compare
47da111
to
24bca7a
Compare
f7d857d
to
977a89e
Compare
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've left a few more asks within, but this is looking sharp.
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'll do some follow-up commits directly to this PR, but I think this mostly LGTM and is ready for an alpha
.
@@ -870,8 +869,10 @@ function makeSendValuesBaseOptionsFromLegacy( | |||
export function computeExecutableSchemaId( | |||
schema: string | GraphQLSchema, | |||
): string { | |||
// Can't call digest on this object twice. Creating new object each function call | |||
const sha256 = module.require('crypto').createHash('sha256'); |
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.
Note to self, I'll tweak this a bit before merging.
}, | ||
}); | ||
|
||
await expect(schemaReporter.reportServerInfo(false)).rejects.toThrow(); |
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 will add a change that checks the error message contents here for specific errors.
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.
Done with 3ddddd4
packages/apollo-engine-reporting/src/__tests__/schemaReporter.test.ts
Outdated
Show resolved
Hide resolved
packages/apollo-engine-reporting/src/__tests__/schemaReporter.test.ts
Outdated
Show resolved
Hide resolved
Having a code editor is nice.
The specificity of the `try`/`catch` around JSON parsing will ensure that HTML parsed as errors is insinuated clearly, whereas any general `fetch` error will propagate up to the error boundary provided by the `catch` that lives within the `reportingLoop` function after `schemaReporter.reportServerInfo` is invoked.
}, | ||
}); | ||
|
||
await expect(schemaReporter.reportServerInfo(false)).rejects.toThrow(); |
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.
Done with 3ddddd4
packages/apollo-engine-reporting/src/__tests__/schemaReporter.test.ts
Outdated
Show resolved
Hide resolved
packages/apollo-engine-reporting/src/__tests__/schemaReporter.test.ts
Outdated
Show resolved
Hide resolved
In the future, this might change back, but this can just be a top-level `import` right now. Ref: https://github.com/apollographql/apollo-server/pull/4084/files#r427103593
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.
Looks great on the whole!
@@ -0,0 +1,64 @@ | |||
/* tslint:disable */ |
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 add an npm script to generate this file to package.json, or at least an explanation somewhere?
* The URL of the Engine report ingress server. | ||
*/ | ||
endpointUrl?: string; | ||
/** | ||
* The URL to the Apollo Graph Manager ingress endpoint. | ||
* (Previously, this was `endpointUrl`, which will be removed in AS3). |
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.
period goes inside parentheses
* **(Experimental)** Enable schema reporting from this server with | ||
* Apollo Graph Manager. | ||
* | ||
* The use of this option avoids the need to rgister schemas manually within |
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.
"register"
|
||
/** | ||
* The schema reporter waits before starting reporting. | ||
* By default, the report waits some random amount of time between 0 and 10 seconds. |
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.
For what it's worth I find this approach to documenting the option a bit confusing.
The point is that there is a randomized delay, from 0 to 10 seconds. It always does that.
This lets you override the 10 second value, not the fact that it's a random amount of time.
This line
By default, the report waits some random amount of time between 0 and 10 seconds.
really should be more like
The report waits a random amount of time between 0 and (by default) 10 seconds.
It's also a bit odd for the first line of this whole comment to be a fact about how SR works rather than directly describing what the option does.
So something more like:
The maximum amount of time in milliseconds that the schema reporter waits before starting to report; defaults to 10 seconds.
The schema reporter waits a random amount of time up to this amount before reporting.
A longer interval leads to more staggered starts which means it is less likely
multiple servers will get asked to upload the same schema.If this server runs in AWS Lambda or in other environments where processes are short-lived,
consider setting this value to a smaller number.
private readonly logger: Logger = console; | ||
private readonly graphVariant: string; | ||
|
||
private reports: { [executableSchemaId: string]: Report } = Object.create( |
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.
(note: i wrote all the below before I realized that the only thing you did to these objects is prettify them. oops. well, it's still true. and i still want to fix it but i don't want to ask you to, so, um, have #4139)
If I understand correctly, there's a strong requirement here that reports, reportSizes, and reportHeaders all have the same keys all the time, and there's a bunch of implicit dependencies on that fact.
Can you instead combine them into a single map whose value type is a tiny class with three fields?
Additionally, the type {[x: string]: Foo}
is actually problematic. It tells TypeScript "no matter what string I index by, I'll get a Foo out". But that's not how this data structure actually works! All of these should be changed into {[x: string]: Foo | undefined}
, which in fact does reveal a bunch of places where we fetch an object from them and don't check to see if it was actually there...
export class SchemaReporter { | ||
// These mirror the gql variables | ||
private readonly serverInfo: EdgeServerInfo; | ||
private readonly executableSchemaDocument: any; |
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 be string
, no?
this.currentSchemaReporter = schemaReporter; | ||
const logger = this.logger; | ||
|
||
setTimeout(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.
It's a little awkward that stop
doesn't clearTimeout this timeout or any of the ones in reportingLoop; even if they won't do anything, you can imagine a context where this will require a program (like a test?) to stay running for up to 10 seconds before doing nothing! Leaking setTimeout return values isn't a great habit to get into anyway. Why not make inner
from reportingLoop
be a method on SchemaReporter, and save the return value from the active timeout on SchemaReporter, and clearTimeout on stop?
): Promise<SchemaReportingServerInfoResult> { | ||
const request: GraphQLRequest = { | ||
query: reportServerInfoGql, | ||
operationName: 'ReportServerInfo', |
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.
There's no need to include this unless you have more than one (named) operation in query
.
const request: GraphQLRequest = { | ||
query: reportServerInfoGql, | ||
operationName: 'ReportServerInfo', | ||
variables: variables, |
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 be just variables,
[ | ||
"Couldn't report server info to Apollo Graph Manager.", | ||
'Parsing response as JSON failed.', | ||
'If this continues please reach out to support@apollographql.com', |
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.
Putting just a space between our email address and the error will read strangely. Newline? Period?
Note: Schema reporting is not currently supported for managed federation
The Apollo Graph Manager schema registry is a free service of Apollo Graph Manager that allows tracking a graph (or variants of it) over time.
Previously, it was necessary to use the
apollo schema:push
command (usually in a CI pipeline) in order to push the latest state of the graph to Apollo Graph Manager and provide this visibility.Apollo schema reporting protocol, is a new specification for GraphQL servers to automatically report schemas to the Apollo Graph Manager schema registry.
This PR implements this protocol for Apollo Server's reporting facilities and can be enabled by providing a Graph Manager API key (available from Apollo Graph Manager) in the
APOLLO_KEY
environment variable and setting theexperimental_schemaReporting
option totrue
in the Apollo Server constructor options, like so:When schema-reporting is enabled, Apollo server will send the running schema and server information to Apollo Graph Manager. The extra runtime information will be used to power auto-promotion which will set a schema as active for a variant, based on the algorithm described in the preview documentation.
When enabled, a schema reporting interval is initiated by the
apollo-engine-reporting
agent. It will loop until theApolloServer
instance is stopped, periodically calling back to Apollo Graph Manager to send information. The life-cycle of this loop is managed by the reporting agent.Additionally, this PR deprecates
schemaHash
in metrics reporting in favor of using the sameexecutableSchemaId
used within this new schema reporting protocol.