Accomodate GQL requests caching in code generator (#237)

* Accomodate GQL requests caching in code generator

* Add GQL API request queuing
This commit is contained in:
prathamesh0
2022-11-17 12:02:08 +05:30
committed by GitHub
parent 79e903b396
commit f53371e17b
17 changed files with 130 additions and 76 deletions
+24 -1
View File
@@ -3,7 +3,7 @@
//
import assert from 'assert';
import { GraphQLSchema, parse, printSchema, print } from 'graphql';
import { GraphQLSchema, parse, printSchema, print, GraphQLDirective, GraphQLInt, GraphQLBoolean } from 'graphql';
import { ObjectTypeComposer, ObjectTypeComposerDefinition, ObjectTypeComposerFieldConfigMapDefinition, SchemaComposer } from 'graphql-compose';
import { Writable } from 'stream';
import { utils } from 'ethers';
@@ -19,6 +19,7 @@ export class Schema {
this._composer = new SchemaComposer();
this._events = [];
this._addGQLCacheTypes();
this._addBasicTypes();
}
@@ -271,6 +272,28 @@ export class Schema {
this._composer.addSchemaMustHaveType(typeComposer);
}
_addGQLCacheTypes (): void {
// Create a enum type composer to add enum CacheControlScope in the schema composer.
const enumTypeComposer = this._composer.createEnumTC(`
enum CacheControlScope {
PUBLIC
PRIVATE
}
`);
this._composer.addSchemaMustHaveType(enumTypeComposer);
// Add the directive cacheControl in the schema composer.
this._composer.addDirective(new GraphQLDirective({
name: 'cacheControl',
locations: ['FIELD_DEFINITION', 'OBJECT', 'INTERFACE', 'UNION'],
args: {
maxAge: { type: GraphQLInt },
inheritMaxAge: { type: GraphQLBoolean },
scope: { type: enumTypeComposer.getType() }
}
}));
}
/**
* Adds types 'ResultEvent' and 'WatchedEvent' to the schema.
*/
@@ -30,6 +30,19 @@
# Use -1 for skipping check on block range.
maxEventsBlockRange = 1000
# GQL cache settings
[server.gqlCache]
enabled = true
# Max in-memory cache size (in bytes) (default 8 MB)
# maxCacheSize
# GQL cache-control max-age settings (in seconds)
maxAge = 15
{{#if (subgraphPath)}}
timeTravelMaxAge = 86400 # 1 day
{{/if}}
[metrics]
host = "127.0.0.1"
port = 9000
@@ -59,6 +59,18 @@
* Edit the custom hook function `createStateCheckpoint` (triggered just before default and CLI checkpoint) in [hooks.ts](./src/hooks.ts) to save the state in a `checkpoint` `State` using the `Indexer` object.
### GQL Caching
To enable GQL requests caching:
* Update the `server.gqlCache` config with required settings.
* In the GQL [schema file](./src/schema.gql), use the `cacheControl` directive to apply cache hints at schema level.
* Eg. Set `inheritMaxAge` to true for non-scalar fields of a type.
* In the GQL [resolvers file](./src/resolvers.ts), uncomment the `setGQLCacheHints()` calls in resolvers for required queries.
## Run
* Run the watcher:
@@ -8,7 +8,7 @@ import debug from 'debug';
import Decimal from 'decimal.js';
import { GraphQLResolveInfo, GraphQLScalarType } from 'graphql';
import { ValueResult, BlockHeight, gqlTotalQueryCount, gqlQueryCount, jsonBigIntStringReplacer, getResultState } from '@cerc-io/util';
import { ValueResult, BlockHeight, gqlTotalQueryCount, gqlQueryCount, jsonBigIntStringReplacer, getResultState, setGQLCacheHints } from '@cerc-io/util';
import { Indexer } from './indexer';
import { EventWatcher } from './events';
@@ -22,6 +22,8 @@ const log = debug('vulcanize:resolver');
export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatcher): Promise<any> => {
assert(indexer);
const gqlCacheConfig = indexer.serverConfig.gqlCache;
return {
BigInt: new BigInt('bigInt'),
@@ -63,14 +65,22 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
Query: {
{{#each queries}}
{{this.name}}: (_: any, { blockHash, contractAddress
{{~#each this.params}}, {{this.name~}} {{/each}} }: { blockHash: string, contractAddress: string
{{~#each this.params}}, {{this.name}}: {{this.type~}} {{/each}} }): Promise<ValueResult> => {
{{this.name}}: (
_: any,
{ blockHash, contractAddress
{{~#each this.params}}, {{this.name~}} {{/each}} }: { blockHash: string, contractAddress: string
{{~#each this.params}}, {{this.name}}: {{this.type~}} {{/each}} },
__: any,
info: GraphQLResolveInfo
): Promise<ValueResult> => {
log('{{this.name}}', blockHash, contractAddress
{{~#each this.params}}, {{this.name~}} {{/each}});
gqlTotalQueryCount.inc(1);
gqlQueryCount.labels('{{this.name}}').inc(1);
// Set cache-control hints
// setGQLCacheHints(info, {}, gqlCacheConfig);
return indexer.{{this.name}}(blockHash, contractAddress
{{~#each this.params}}, {{this.name~}} {{/each}});
},
@@ -79,7 +89,7 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
{{~#each subgraphQueries}}
{{this.queryName}}: async (
_: any,
_: any,
{ id, block = {} }: { id: string, block: BlockHeight },
__: any,
info: GraphQLResolveInfo
@@ -89,6 +99,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('{{this.queryName}}').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
// setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity({{this.entityName}}, id, block, info.fieldNodes[0].selectionSet.selections);
},
@@ -39,7 +39,7 @@ export const main = async (): Promise<any> => {
const config: Config = await getConfig(argv.f);
const { ethClient, ethProvider } = await initClients(config);
const { host, port, kind: watcherKind } = config.server;
const { kind: watcherKind } = config.server;
const db = new Database(config.database);
await db.init();
@@ -85,7 +85,7 @@ export const main = async (): Promise<any> => {
// Create an Express app
const app: Application = express();
const server = createAndStartServer(app, typeDefs, resolvers, { host, port });
const server = createAndStartServer(app, typeDefs, resolvers, config.server);
startGQLMetricsServer(config);