Skip to content

Commit

Permalink
Merge pull request #208 from utxostack/feat/cache-control
Browse files Browse the repository at this point in the history
feat: cache control
  • Loading branch information
ahonn authored Sep 26, 2024
2 parents d90433e + eae0c44 commit e057ef0
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 21 deletions.
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"dependencies": {
"@apollo/server": "^4.11.0",
"@apollo/server-plugin-response-cache": "^4.1.3",
"@as-integrations/fastify": "^2.1.1",
"@cell-studio/mempool.js": "^2.5.3",
"@ckb-lumos/bi": "^0.23.0",
Expand Down
5 changes: 0 additions & 5 deletions backend/src/core/bitcoin-api/bitcoin-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { ChainInfo, Transaction } from './bitcoin-api.schema';
import { ONE_HOUR_MS, ONE_MONTH_MS, TEN_MINUTES_MS } from 'src/common/date';
import { Cacheable } from 'src/decorators/cacheable.decorator';
import * as Sentry from '@sentry/nestjs';
import { PLimit } from 'src/decorators/plimit.decorator';

type MethodParameters<T, K extends keyof T> = T[K] extends (...args: infer P) => any ? P : never;
type MethodReturnType<T, K extends keyof T> = T[K] extends (...args: any[]) => infer R ? R : never;
Expand Down Expand Up @@ -180,7 +179,6 @@ export class BitcoinApiService {
key: ({ address }) => `getAddress:${address}`,
ttl: 10_000,
})
@PLimit({ concurrency: 100 })
public async getAddress({ address }: { address: string }) {
return this.call('getAddress', { address });
}
Expand All @@ -190,7 +188,6 @@ export class BitcoinApiService {
key: ({ address }) => `getAddressTxsUtxo:${address}`,
ttl: 10_000,
})
@PLimit({ concurrency: 100 })
public async getAddressTxsUtxo({ address }: { address: string }) {
return this.call('getAddressTxsUtxo', { address });
}
Expand All @@ -200,7 +197,6 @@ export class BitcoinApiService {
key: ({ address, afterTxid }) => `getAddressTxs:${address}:${afterTxid}`,
ttl: 10_000,
})
@PLimit({ concurrency: 100 })
public async getAddressTxs({ address, afterTxid }: { address: string; afterTxid?: string }) {
return this.call('getAddressTxs', { address, afterTxid });
}
Expand Down Expand Up @@ -236,7 +232,6 @@ export class BitcoinApiService {
key: ({ txids }) => `getTransactionTimes:${txids.join(',')}`,
ttl: 10_000,
})
@PLimit({ concurrency: 100 })
public async getTransactionTimes({ txids }: { txids: string[] }) {
return this.call('getTransactionTimes', { txids });
}
Expand Down
2 changes: 0 additions & 2 deletions backend/src/core/blockchain/blockchain.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { ONE_MONTH_MS } from 'src/common/date';
import { CKB_MIN_SAFE_CONFIRMATIONS } from 'src/constants';
import * as Sentry from '@sentry/nestjs';
import { Chain } from '@prisma/client';
import { PLimit } from 'src/decorators/plimit.decorator';

class WebsocketError extends Error {
constructor(message: string) {
Expand Down Expand Up @@ -249,7 +248,6 @@ export class BlockchainService {
return BI.from(tipBlockNumber).toNumber();
}

@PLimit({ concurrency: 100 })
public async getTransactions(
searchKey: SearchKey,
order: 'asc' | 'desc',
Expand Down
19 changes: 19 additions & 0 deletions backend/src/decorators/cache-control.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Directive } from '@nestjs/graphql';

interface CacheControlOptions {
maxAge?: number;
scope?: 'PRIVATE' | 'PUBLIC';
inheritMaxAge?: boolean;
}

export const CacheControl = ({ maxAge, scope = 'PUBLIC', inheritMaxAge }: CacheControlOptions) => {
const args = [
`scope: ${scope}`,
maxAge !== undefined ? `maxAge: ${maxAge}` : null,
inheritMaxAge ? `inheritMaxAge: ${inheritMaxAge}` : null,
]
.filter(Boolean)
.join(', ');

return Directive(`@cacheControl(${args})`);
};
58 changes: 54 additions & 4 deletions backend/src/modules/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { join } from 'node:path';
import { ExecutionContext, Injectable, Module } from '@nestjs/common';
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { ConfigService } from '@nestjs/config';
import { GqlExecutionContext, GraphQLModule } from '@nestjs/graphql';
import { GqlExecutionContext, GraphQLModule, Int } from '@nestjs/graphql';
import { DataLoaderInterceptor } from 'src/common/dataloader';
import { Env } from 'src/env';
import { CkbModule } from './ckb/ckb.module';
Expand All @@ -15,8 +15,12 @@ import { ComplexityPlugin } from './complexity.plugin';
import * as Sentry from '@sentry/nestjs';
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
import { FastifyReply, FastifyRequest } from 'fastify';
import { SentryGlobalGraphQLFilter } from '@sentry/nestjs/setup';
import { ApolloServerPluginCacheControl } from '@apollo/server/plugin/cacheControl';
import responseCachePlugin from '@apollo/server-plugin-response-cache';
import { AllExceptionsFilter } from 'src/filters/all-exceptions.filter';
import { DirectiveLocation, GraphQLBoolean, GraphQLDirective, GraphQLEnumType } from 'graphql';
import { LoggingPlugin } from './logging.plugin';
import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager';

@Injectable()
export class GqlThrottlerGuard extends ThrottlerGuard {
Expand All @@ -42,16 +46,61 @@ export class GqlThrottlerGuard extends ThrottlerGuard {
}),
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
inject: [ConfigService],
useFactory: async (configService: ConfigService<Env>) => ({
inject: [ConfigService, CACHE_MANAGER],
useFactory: async (configService: ConfigService<Env>, cacheManager: Cache) => ({
playground: configService.get('ENABLED_GRAPHQL_PLAYGROUND'),
installSubscriptionHandlers: true,
introspection: true,
graphiql: true,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
plugins: [
ApolloServerPluginCacheControl({
defaultMaxAge: 10,
calculateHttpHeaders: true,
}),
responseCachePlugin(),
],
cache: {
async get(key: string) {
const val = await cacheManager.get(key);
return val as string | undefined;
},
async set(key: string, value: string, options?: { ttl: number | null }) {
const { ttl } = options || { ttl: null };
await cacheManager.set(key, value, ttl ? ttl * 1000 : undefined);
},
async delete(key: string) {
await cacheManager.del(key);
},
},
buildSchemaOptions: {
dateScalarMode: 'timestamp',
fieldMiddleware: [fieldPerformanceMiddleware],
directives: [
new GraphQLDirective({
name: 'cacheControl',
args: {
maxAge: { type: Int },
scope: {
type: new GraphQLEnumType({
name: 'CacheControlScope',
values: {
PUBLIC: {},
PRIVATE: {},
},
}),
},
inheritMaxAge: { type: GraphQLBoolean },
},
locations: [
DirectiveLocation.FIELD_DEFINITION,
DirectiveLocation.OBJECT,
DirectiveLocation.INTERFACE,
DirectiveLocation.UNION,
DirectiveLocation.QUERY,
],
}),
],
},
context: (request: FastifyRequest, reply: FastifyReply) => {
return {
Expand Down Expand Up @@ -84,6 +133,7 @@ export class GqlThrottlerGuard extends ThrottlerGuard {
useClass: AllExceptionsFilter,
},
ComplexityPlugin,
LoggingPlugin,
],
})
export class ApiModule { }
27 changes: 27 additions & 0 deletions backend/src/modules/logging.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ApolloServerPlugin, GraphQLRequestContext, GraphQLRequestListener } from '@apollo/server';
import { Plugin } from '@nestjs/apollo';
import { Logger } from '@nestjs/common';
import { FastifyRequest } from 'fastify';

interface Context {
request: FastifyRequest;
}

@Plugin()
export class LoggingPlugin implements ApolloServerPlugin {
private readonly logger = new Logger(LoggingPlugin.name);

async requestDidStart(requestContext: GraphQLRequestContext<Context>): Promise<GraphQLRequestListener<any>> {
const { request } = requestContext.contextValue;
const body = request.body as { operationName: string; variables: Record<string, any>, query: string };
this.logger.log(`Request [${request.ip}] ${body.operationName} ${JSON.stringify(body.variables)}`);

const start = performance.now();
return {
willSendResponse: async () => {
const end = performance.now();
this.logger.log(`Response [${request.ip}] ${body.operationName} ${end - start}ms`);
},
};
}
}
7 changes: 7 additions & 0 deletions backend/src/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------

directive @cacheControl(maxAge: Int, scope: CacheControlScope, inheritMaxAge: Boolean) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | QUERY

"""CKB Script"""
type CkbScript {
codeHash: String!
Expand Down Expand Up @@ -362,4 +364,9 @@ enum TransactionListSortType {
AddressCountDesc
CreatedTimeAsc
CreatedTimeDesc
}

enum CacheControlScope {
PUBLIC
PRIVATE
}
5 changes: 5 additions & 0 deletions frontend/src/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ export type BitcoinTransaction = {
weight: Scalars['Float']['output'];
};

export enum CacheControlScope {
Private = 'PRIVATE',
Public = 'PUBLIC'
}

/** Cell type (XUDT, SUDT, Dobs, mNFT) */
export enum CellType {
Dob = 'DOB',
Expand Down
37 changes: 27 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e057ef0

Please sign in to comment.