Add GQL requests caching in eden-watcher (#234)

This commit is contained in:
prathamesh0 2022-11-16 08:31:18 -06:00 committed by GitHub
parent 74741184ee
commit 1ad223db4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 190 additions and 26 deletions

View File

@ -28,6 +28,17 @@
# Interval in number of blocks at which to clear entities cache.
clearEntitiesCacheInterval = 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
timeTravelMaxAge = 86400 # 1 day
[metrics]
host = "127.0.0.1"
port = 9000

View File

@ -8,7 +8,7 @@ import debug from 'debug';
import Decimal from 'decimal.js';
import { GraphQLResolveInfo, GraphQLScalarType } from 'graphql';
import { BlockHeight, OrderDirection, gqlTotalQueryCount, gqlQueryCount, jsonBigIntStringReplacer, getResultState } from '@cerc-io/util';
import { BlockHeight, OrderDirection, gqlTotalQueryCount, gqlQueryCount, jsonBigIntStringReplacer, getResultState, setGQLCacheHints } from '@cerc-io/util';
import { Indexer } from './indexer';
import { EventWatcher } from './events';
@ -37,6 +37,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'),
@ -88,6 +90,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('producer').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(Producer, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -102,6 +107,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('producers').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntities(
Producer,
block,
@ -122,6 +130,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('producerSet').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(ProducerSet, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -136,6 +147,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('producerSetChange').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(ProducerSetChange, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -150,6 +164,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('producerRewardCollectorChange').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(ProducerRewardCollectorChange, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -164,6 +181,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('rewardScheduleEntry').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(RewardScheduleEntry, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -178,6 +198,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('rewardSchedule').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(RewardSchedule, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -192,6 +215,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('producerEpoch').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(ProducerEpoch, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -206,6 +232,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('block').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(Block, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -220,6 +249,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('blocks').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntities(
Block,
block,
@ -240,6 +272,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('epoch').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(Epoch, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -254,6 +289,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('epoches').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntities(
Epoch,
block,
@ -274,6 +312,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('slotClaim').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(SlotClaim, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -288,6 +329,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('slot').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(Slot, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -302,6 +346,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('staker').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(Staker, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -316,6 +363,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('stakers').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntities(
Staker,
block,
@ -336,6 +386,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('network').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(Network, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -364,6 +417,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('distribution').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(Distribution, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -378,6 +434,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('claim').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(Claim, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -392,6 +451,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('slash').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(Slash, id, block, info.fieldNodes[0].selectionSet.selections);
},
@ -406,6 +468,9 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
gqlQueryCount.labels('account').inc(1);
assert(info.fieldNodes[0].selectionSet);
// Set cache-control hints
setGQLCacheHints(info, block, gqlCacheConfig);
return indexer.getSubgraphEntity(Account, id, block, info.fieldNodes[0].selectionSet.selections);
},

View File

@ -1,3 +1,14 @@
enum CacheControlScope {
PUBLIC
PRIVATE
}
directive @cacheControl(
maxAge: Int
scope: CacheControlScope
inheritMaxAge: Boolean
) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
scalar BigInt
scalar Bytes
@ -264,14 +275,14 @@ type Producer {
type ProducerSet {
id: ID!
producers: [Producer!]!
producers: [Producer!]! @cacheControl(inheritMaxAge: true)
}
type ProducerSetChange {
id: ID!
blockNumber: BigInt!
producer: Bytes!
changeType: ProducerSetChangeType!
changeType: ProducerSetChangeType! @cacheControl(inheritMaxAge: true)
}
enum ProducerSetChangeType {
@ -295,10 +306,10 @@ type RewardScheduleEntry {
type RewardSchedule {
id: ID!
rewardScheduleEntries: [RewardScheduleEntry!]!
lastEpoch: Epoch
pendingEpoch: Epoch
activeRewardScheduleEntry: RewardScheduleEntry
rewardScheduleEntries: [RewardScheduleEntry!]! @cacheControl(inheritMaxAge: true)
lastEpoch: Epoch @cacheControl(inheritMaxAge: true)
pendingEpoch: Epoch @cacheControl(inheritMaxAge: true)
activeRewardScheduleEntry: RewardScheduleEntry @cacheControl(inheritMaxAge: true)
}
type Block {
@ -336,12 +347,12 @@ type Epoch {
id: ID!
finalized: Boolean!
epochNumber: BigInt!
startBlock: Block
endBlock: Block
startBlock: Block @cacheControl(inheritMaxAge: true)
endBlock: Block @cacheControl(inheritMaxAge: true)
producerBlocks: BigInt!
allBlocks: BigInt!
producerBlocksRatio: BigDecimal!
producerRewards: [ProducerEpoch!]!
producerRewards: [ProducerEpoch!]! @cacheControl(inheritMaxAge: true)
}
input Epoch_filter {
@ -352,7 +363,7 @@ input Epoch_filter {
type ProducerEpoch {
id: ID!
address: Bytes!
epoch: Epoch!
epoch: Epoch! @cacheControl(inheritMaxAge: true)
totalRewards: BigInt!
blocksProduced: BigInt!
blocksProducedRatio: BigDecimal!
@ -360,7 +371,7 @@ type ProducerEpoch {
type SlotClaim {
id: ID!
slot: Slot!
slot: Slot! @cacheControl(inheritMaxAge: true)
owner: Bytes!
winningBid: BigInt!
oldBid: BigInt!
@ -378,7 +389,7 @@ type Slot {
startTime: BigInt!
expirationTime: BigInt!
taxRatePerDay: BigDecimal!
claims: [SlotClaim!]!
claims: [SlotClaim!]! @cacheControl(inheritMaxAge: true)
}
type Staker {
@ -397,10 +408,10 @@ enum Staker_orderBy {
type Network {
id: ID!
slot0: Slot
slot1: Slot
slot2: Slot
stakers: [Staker!]!
slot0: Slot @cacheControl(inheritMaxAge: true)
slot1: Slot @cacheControl(inheritMaxAge: true)
slot2: Slot @cacheControl(inheritMaxAge: true)
stakers: [Staker!]! @cacheControl(inheritMaxAge: true)
numStakers: BigInt
totalStaked: BigInt!
stakedPercentiles: [BigInt!]!
@ -408,12 +419,12 @@ type Network {
type Distributor {
id: ID!
currentDistribution: Distribution
currentDistribution: Distribution @cacheControl(inheritMaxAge: true)
}
type Distribution {
id: ID!
distributor: Distributor!
distributor: Distributor! @cacheControl(inheritMaxAge: true)
timestamp: BigInt!
distributionNumber: BigInt!
merkleRoot: Bytes!
@ -424,7 +435,7 @@ type Claim {
id: ID!
timestamp: BigInt!
index: BigInt!
account: Account!
account: Account! @cacheControl(inheritMaxAge: true)
totalEarned: BigInt!
claimed: BigInt!
}
@ -433,14 +444,14 @@ type Account {
id: ID!
totalClaimed: BigInt!
totalSlashed: BigInt!
claims: [Claim!]!
slashes: [Slash!]!
claims: [Claim!]! @cacheControl(inheritMaxAge: true)
slashes: [Slash!]! @cacheControl(inheritMaxAge: true)
}
type Slash {
id: ID!
timestamp: BigInt!
account: Account!
account: Account! @cacheControl(inheritMaxAge: true)
slashed: BigInt!
}

View File

@ -13,7 +13,7 @@ import { hideBin } from 'yargs/helpers';
import debug from 'debug';
import 'graphql-import-node';
import { DEFAULT_CONFIG_PATH, getConfig, Config, JobQueue, KIND_ACTIVE, initClients, startGQLMetricsServer, createAndStartServer } from '@cerc-io/util';
import { DEFAULT_CONFIG_PATH, getConfig, Config, JobQueue, KIND_ACTIVE, initClients, startGQLMetricsServer, createAndStartServerWithCache } from '@cerc-io/util';
import { GraphWatcher, Database as GraphDatabase } from '@cerc-io/graph-node';
import { createResolvers } from './resolvers';
@ -37,7 +37,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();
@ -79,7 +79,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 = createAndStartServerWithCache(app, typeDefs, resolvers, config.server);
startGQLMetricsServer(config);

View File

@ -4,11 +4,13 @@
"main": "dist/index.js",
"license": "AGPL-3.0",
"dependencies": {
"@apollo/utils.keyvaluecache": "^1.0.1",
"@cerc-io/solidity-mapper": "^0.2.13",
"@graphql-tools/schema": "^9.0.10",
"@graphql-tools/utils": "^9.1.1",
"apollo-server-core": "^3.11.1",
"apollo-server-express": "^3.11.1",
"apollo-server-plugin-response-cache": "^3.8.1",
"debug": "^4.3.1",
"decimal.js": "^10.3.1",
"ethers": "^5.4.4",

View File

@ -5,12 +5,78 @@ import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';
import debug from 'debug';
import responseCachePlugin from 'apollo-server-plugin-response-cache';
import { InMemoryLRUCache } from '@apollo/utils.keyvaluecache';
import { TypeSource } from '@graphql-tools/utils';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { DEFAULT_MAX_GQL_CACHE_SIZE } from './constants';
import { ServerConfig } from './config';
const log = debug('vulcanize:server');
export const createAndStartServerWithCache = async (
app: Application,
typeDefs: TypeSource,
resolvers: any,
serverConfig: ServerConfig
): Promise<ApolloServer> => {
const host = serverConfig.host;
const port = serverConfig.port;
const gqlCacheConfig = serverConfig.gqlCache;
// Create HTTP server
const httpServer = createServer(app);
// Create the schema
const schema = makeExecutableSchema({ typeDefs, resolvers });
// Create our WebSocket server using the HTTP server we just set up.
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql'
});
const serverCleanup = useServer({ schema }, wsServer);
// Setup in-memory GQL cache
let gqlCache;
if (gqlCacheConfig && gqlCacheConfig.enabled) {
const maxSize = gqlCacheConfig.maxCacheSize ? gqlCacheConfig.maxCacheSize : DEFAULT_MAX_GQL_CACHE_SIZE;
gqlCache = new InMemoryLRUCache({ maxSize });
}
const server = new ApolloServer({
schema,
csrfPrevention: true,
cache: gqlCache,
plugins: [
// Proper shutdown for the HTTP server
ApolloServerPluginDrainHttpServer({ httpServer }),
// Proper shutdown for the WebSocket server
{
async serverWillStart () {
return {
async drainServer () {
await serverCleanup.dispose();
}
};
}
},
// GQL response cache plugin
responseCachePlugin()
]
});
await server.start();
server.applyMiddleware({ app });
httpServer.listen(port, host, () => {
log(`Server is listening on ${host}:${port}${server.graphqlPath}`);
});
return server;
};
export const createAndStartServer = async (
app: Application,
typeDefs: TypeSource,

View File

@ -3502,6 +3502,15 @@ apollo-server-plugin-base@^3.7.1:
dependencies:
apollo-server-types "^3.7.1"
apollo-server-plugin-response-cache@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/apollo-server-plugin-response-cache/-/apollo-server-plugin-response-cache-3.8.1.tgz#2d9559fe9951812dcda77fc2658422028f060af8"
integrity sha512-c6bkrZjzX8Ac44CF7a4m9ans2QFV2C0XVUUDnuSWAX3G9ZWbs55NC+H3YtcFhAPoX7gv92RAu88ipMahd8NiUQ==
dependencies:
"@apollo/utils.keyvaluecache" "^1.0.1"
apollo-server-plugin-base "^3.7.1"
apollo-server-types "^3.7.1"
apollo-server-types@^3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-3.7.1.tgz#87adfcb52ec0893999a9cfafd5474bfda7ab0798"