This project is aimed to provide actual query duration statistics from apollo-studio.
These timing metrics (AKA timing-hints) can then be utilized in a custom graphql complexity estimator and plugged into the graphql-query-complexity library designed to "... protect your GraphQL servers against resource exhaustion and DoS attacks".
- create a
schema.graphql
containing the AST version of your graphql schema inside thedata
folder - create a
.env
file, based on.env.example
- build with
yarn
- run with
yarn run
- logins into apollo-studio using puppeteer and intercepts your auth cookies, and the timing metrics persistent query hash
- reads your schema from
schema.graphql
, and runs a graphql timing metric query, via apollo-studio graphql api (using cookies and persistent query hash from [1]) - arranges the data into an object of the following structure:
{
"Address.city": 0.003780965612170304,
"Address.country": 0.0036712738793939926,
"Address.entrance": 0.0037941881736387904,
...
}
- stores this data in an aws s3 bucket
Note: you can (and it makes sense to) run the above logic periodically.
In your apollo-server
implementation, add a plugin:
import { getComplexity, simpleEstimator } from 'graphql-query-complexity';
...
class ComplexityValidator implements ApolloServerPlugin<any> {
...
requestDidStart(requestContext: GraphQLRequestContext<any>): GraphQLRequestListener<any> | void {
return {
didResolveOperation({ request, document }) {
const query = request.operationName
? separateOperations(document)[request.operationName]
: document;
const complexity = getComplexity({
schema,
query,
variables: request.variables,
estimators: [
// where timingMetrics is the data generated by this (apollo-studio-data-fetcher) project
timingBasedEstimator(timingMetrics),
simpleEstimator({ defaultComplexity: 100 })
],
});
validateComplexity(...);
}
};
}
}
while the timingBasedEstimator implementation can be something along these lines:
import { ComplexityEstimatorArgs } from 'graphql-query-complexity';
import { get } from 'lodash';
/**
* Complexity estimator based on actual query timing metrics fetched from apollo-studio.
* The return unit is milliseconds-equivalent.
*
* @param timingMetrics
*/
export function timingBasedEstimator(timingMetrics) {
return (options: ComplexityEstimatorArgs) => {
const timingMetricKey = `${options.type.name}.${options.field.name}`;
const timingMetricValue = timingMetrics[timingMetricKey];
if (timingMetricValue) {
// Your actual calculation might be different, using such a multiplier, is just an examle.
const multiplier = Math.max(get(options, 'args.first', 1), get(options, 'args.last', 1));
return (timingMetricValue + options.childComplexity) * multiplier;
}
return undefined; // Fallback to simpleEstimator.
};
}
- add some tests
- support mutation / interfaces / unions etc.