Skip to content

Commit

Permalink
Merge pull request #666 from input-output-hk/feat/optimised-healthcheck
Browse files Browse the repository at this point in the history
feat: Optimised health checks with dedicated pool
  • Loading branch information
mkazlauskas committed Mar 29, 2023
2 parents 80113f9 + 4729889 commit c66683f
Show file tree
Hide file tree
Showing 39 changed files with 742 additions and 323 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ export class DbSyncAssetProvider extends DbSyncProvider() implements AssetProvid
#dependencies: DbSyncAssetProviderDependencies;

constructor(dependencies: DbSyncAssetProviderDependencies) {
const { db, cardanoNode, logger } = dependencies;
super({ cardanoNode, db, logger });
const { cache, dbPools, cardanoNode, logger } = dependencies;
super({ cache, cardanoNode, dbPools, logger });

this.#builder = new AssetBuilder(db, logger);
this.#builder = new AssetBuilder(dbPools.main, logger);
this.#dependencies = dependencies;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AvailableNetworks } from '..';
import { AvailableNetworks } from '../Program/programs/blockfrostWorker';
import { BlockFrostAPI } from '@blockfrost/blockfrost-js';
import { BlockfrostCacheBuilder } from './builder';
import { Cardano, Provider } from '@cardano-sdk/core';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Queries from './queries';
import { Cardano, PaginationArgs, Range } from '@cardano-sdk/core';
import { Cardano, PaginationArgs } from '@cardano-sdk/core';
import {
CertificateModel,
CountModel,
Expand All @@ -26,7 +26,7 @@ import {
import { DB_MAX_SAFE_INTEGER, findTxsByAddresses } from './queries';
import { Logger } from 'ts-log';
import { Pool, QueryResult } from 'pg';
import { hexStringToBuffer } from '@cardano-sdk/util';
import { Range, hexStringToBuffer } from '@cardano-sdk/util';
import {
mapCertificate,
mapRedeemer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ export class DbSyncChainHistoryProvider extends DbSyncProvider() implements Chai

constructor(
{ paginationPageSizeLimit }: ChainHistoryProviderProps,
{ db, cardanoNode, metadataService, logger }: ChainHistoryProviderDependencies
{ cache, dbPools, cardanoNode, metadataService, logger }: ChainHistoryProviderDependencies
) {
super({ cardanoNode, db, logger });
this.#builder = new ChainHistoryBuilder(db, logger);
super({ cache, cardanoNode, dbPools, logger });
this.#builder = new ChainHistoryBuilder(dbPools.main, logger);
this.#metadataService = metadataService;
this.#paginationPageSizeLimit = paginationPageSizeLimit;
}
Expand Down Expand Up @@ -109,7 +109,7 @@ export class DbSyncChainHistoryProvider extends DbSyncProvider() implements Chai

private async transactionsByIds(ids: string[]): Promise<Cardano.HydratedTx[]> {
this.logger.debug('About to find transactions with ids:', ids);
const txResults: QueryResult<TxModel> = await this.db.query(Queries.findTransactionsByIds, [ids]);
const txResults: QueryResult<TxModel> = await this.dbPools.main.query(Queries.findTransactionsByIds, [ids]);
if (txResults.rows.length === 0) return [];

const [inputs, outputs, mints, withdrawals, redeemers, metadata, collaterals, certificates] = await Promise.all([
Expand Down Expand Up @@ -156,19 +156,20 @@ export class DbSyncChainHistoryProvider extends DbSyncProvider() implements Chai
}

this.logger.debug('About to find network tip');
const tipResult: QueryResult<TipModel> = await this.db.query(Queries.findTip);
const tipResult: QueryResult<TipModel> = await this.dbPools.main.query(Queries.findTip);
const tip: TipModel = tipResult.rows[0];
if (!tip) return [];

const byteIds = ids.map((id) => hexStringToBuffer(id));
this.logger.debug('About to find blocks with hashes:', byteIds);
const blocksResult: QueryResult<BlockModel> = await this.db.query(Queries.findBlocksByHashes, [byteIds]);
const blocksResult: QueryResult<BlockModel> = await this.dbPools.main.query(Queries.findBlocksByHashes, [byteIds]);
if (blocksResult.rows.length === 0) return [];

this.logger.debug('About to find blocks outputs and fees for blocks:', byteIds);
const outputResult: QueryResult<BlockOutputModel> = await this.db.query(Queries.findBlocksOutputByHashes, [
byteIds
]);
const outputResult: QueryResult<BlockOutputModel> = await this.dbPools.main.query(
Queries.findBlocksOutputByHashes,
[byteIds]
);

return blocksResult.rows.map((block) => {
const blockOutput = outputResult.rows.find((output) => output.hash === block.hash) ?? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class InMemoryCache {
}

const resultPromise = asyncAction();
this.#cache.set(
this.set(
key,
resultPromise.catch(() => this.#cache.del(key)),
ttl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export interface NetworkInfoProviderDependencies extends DbSyncProviderDependenc
/**
* The in memory cache engine.
*/
cache: InMemoryCache;
cache: DbSyncProviderDependencies['cache'] & {
db: InMemoryCache;
};

/**
* Monitor the epoch rollover through db polling.
Expand All @@ -49,14 +51,14 @@ export class DbSyncNetworkInfoProvider extends DbSyncProvider(RunnableModule) im
#epochRolloverDisposer: Disposer;
#slotEpochCalc: SlotEpochCalc;

constructor({ cache, cardanoNode, db, epochMonitor, genesisData, logger }: NetworkInfoProviderDependencies) {
super({ cardanoNode, db, logger }, 'DbSyncNetworkInfoProvider', logger);
constructor({ cache, cardanoNode, dbPools, epochMonitor, genesisData, logger }: NetworkInfoProviderDependencies) {
super({ cache, cardanoNode, dbPools, logger }, 'DbSyncNetworkInfoProvider', logger);

this.#logger = logger;
this.#cache = cache;
this.#cache = cache.db;
this.#currentEpoch = Cardano.EpochNo(0);
this.#epochMonitor = epochMonitor;
this.#builder = new NetworkInfoBuilder(db, logger);
this.#builder = new NetworkInfoBuilder(dbPools.main, logger);
this.#genesisData = genesisData;
}

Expand Down
12 changes: 11 additions & 1 deletion packages/cardano-services/src/Program/options/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ import {
} from '../utils';
import { BuildInfo as ServiceBuildInfo } from '../../Http';
import { URL } from 'url';
import { buildInfoValidator } from '../../util/validators';
import { buildInfoValidator, cacheTtlValidator } from '../../util/validators';
import { loggerMethodNames } from '@cardano-sdk/util';

export const ENABLE_METRICS_DEFAULT = false;
export const DEFAULT_HEALTH_CHECK_CACHE_TTL = 5;

export enum CommonOptionDescriptions {
ApiUrl = 'API URL',
BuildInfo = 'Service build info',
LoggerMinSeverity = 'Log level',
HealthCheckCacheTtl = 'Health check cache TTL in seconds between 1 and 10',
EnableMetrics = 'Enable Prometheus Metrics',
ServiceDiscoveryBackoffFactor = 'Exponential backoff factor for service discovery',
ServiceDiscoveryTimeout = 'Timeout for service discovery attempts'
Expand Down Expand Up @@ -53,6 +55,14 @@ export const withCommonOptions = (command: Command, defaults: { apiUrl: URL }) =
stringOptionToBoolean(enableMetrics, Programs.ProviderServer, CommonOptionDescriptions.EnableMetrics)
)
)
.addOption(
new Option('--health-check-cache-ttl <healthCheckCacheTTL>', CommonOptionDescriptions.HealthCheckCacheTtl)
.env('HEALTH_CHECK_CACHE_TTL')
.default(DEFAULT_HEALTH_CHECK_CACHE_TTL)
.argParser((ttl: string) =>
cacheTtlValidator(ttl, { lowerBound: 1, upperBound: 120 }, CommonOptionDescriptions.HealthCheckCacheTtl)
)
)
.addOption(
new Option('--logger-min-severity <level>', CommonOptionDescriptions.LoggerMinSeverity)
.env('LOGGER_MIN_SEVERITY')
Expand Down
101 changes: 71 additions & 30 deletions packages/cardano-services/src/Program/programs/providerServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { AssetHttpService } from '../../Asset/AssetHttpService';
import { CardanoNode } from '@cardano-sdk/core';
import { CardanoTokenRegistry } from '../../Asset/CardanoTokenRegistry';
import { ChainHistoryHttpService, DbSyncChainHistoryProvider } from '../../ChainHistory';
import { DbPools, DbSyncEpochPollService, loadGenesisData } from '../../util';
import { DbSyncAssetProvider } from '../../Asset/DbSyncAssetProvider';
import { DbSyncEpochPollService, loadGenesisData } from '../../util';
import { DbSyncNetworkInfoProvider, NetworkInfoHttpService } from '../../NetworkInfo';
import { DbSyncNftMetadataService, StubTokenMetadataService } from '../../Asset';
import { DbSyncRewardsProvider, RewardsHttpService } from '../../Rewards';
Expand All @@ -18,6 +18,7 @@ import { InMemoryCache, NoCache } from '../../InMemoryCache';
import { Logger } from 'ts-log';
import { MissingProgramOption, MissingServiceDependency, RunnableDependencies, UnknownServiceName } from '../errors';
import { OgmiosCardanoNode } from '@cardano-sdk/ogmios';
import { Pool } from 'pg';
import { PostgresOptionDescriptions } from '../options/postgres';
import { ProviderServerArgs, ProviderServerOptionDescriptions, ServiceNames } from './types';
import { SrvRecord } from 'dns';
Expand All @@ -28,7 +29,6 @@ import { createLogger } from 'bunyan';
import { getOgmiosCardanoNode, getOgmiosTxSubmitProvider, getPool, getRabbitMqTxSubmitProvider } from '../services';
import { isNotNil } from '@cardano-sdk/util';
import memoize from 'lodash/memoize';
import pg from 'pg';

export const DISABLE_DB_CACHE_DEFAULT = false;
export const DISABLE_STAKE_POOL_METRIC_APY_DEFAULT = false;
Expand All @@ -53,54 +53,61 @@ export interface LoadProviderServerDependencies {

interface ServiceMapFactoryOptions {
args: ProviderServerArgs;
dbConnection?: pg.Pool;
pools: Partial<DbPools>;
dnsResolver: DnsResolver;
genesisData?: GenesisData;
logger: Logger;
node?: OgmiosCardanoNode;
}

const serviceMapFactory = (options: ServiceMapFactoryOptions) => {
const { args, dbConnection, dnsResolver, genesisData, logger, node } = options;
const { args, pools, dnsResolver, genesisData, logger, node } = options;
const withDbSyncProvider =
<T>(factory: (db: pg.Pool, cardanoNode: CardanoNode) => T, serviceName: ServiceNames) =>
<T>(factory: (dbPools: DbPools, cardanoNode: CardanoNode) => T, serviceName: ServiceNames) =>
() => {
if (!dbConnection)
if (!pools.main || !pools.healthCheck)
throw new MissingProgramOption(serviceName, [
PostgresOptionDescriptions.ConnectionString,
PostgresOptionDescriptions.ServiceDiscoveryArgs
]);

if (!node) throw new MissingServiceDependency(serviceName, RunnableDependencies.CardanoNode);

return factory(dbConnection, node);
return factory(pools as DbPools, node);
};

const getCache = () => (args.disableDbCache ? new NoCache() : new InMemoryCache(args.dbCacheTtl!));
const getCache = (ttl: number) => (args.disableDbCache ? new NoCache() : new InMemoryCache(ttl));
const getDbCache = () => getCache(args.dbCacheTtl);

const getEpochMonitor = memoize((dbPool) => new DbSyncEpochPollService(dbPool, args.epochPollInterval!));
// Shared cache across all providers
const healthCheckCache = getCache(args.healthCheckCacheTtl);

const getEpochMonitor = memoize((dbPool: Pool) => new DbSyncEpochPollService(dbPool, args.epochPollInterval!));

return {
[ServiceNames.Asset]: withDbSyncProvider(async (db, cardanoNode) => {
[ServiceNames.Asset]: withDbSyncProvider(async (dbPools, cardanoNode) => {
const ntfMetadataService = new DbSyncNftMetadataService({
db,
db: dbPools.main,
logger,
metadataService: createDbSyncMetadataService(db, logger)
metadataService: createDbSyncMetadataService(dbPools.main, logger)
});
const tokenMetadataService = args.tokenMetadataServerUrl?.startsWith('stub:')
? new StubTokenMetadataService()
: new CardanoTokenRegistry({ logger }, args);
const assetProvider = new DbSyncAssetProvider({
cache: {
healthCheck: healthCheckCache
},
cardanoNode,
db,
dbPools,
logger,
ntfMetadataService,
tokenMetadataService
});

return new AssetHttpService({ assetProvider, logger });
}, ServiceNames.Asset),
[ServiceNames.StakePool]: withDbSyncProvider(async (db, cardanoNode) => {
[ServiceNames.StakePool]: withDbSyncProvider(async (dbPools, cardanoNode) => {
if (!genesisData)
throw new MissingProgramOption(ServiceNames.StakePool, ProviderServerOptionDescriptions.CardanoNodeConfigPath);
const stakePoolProvider = new DbSyncStakePoolProvider(
Expand All @@ -110,10 +117,13 @@ const serviceMapFactory = (options: ServiceMapFactoryOptions) => {
useBlockfrost: args.useBlockfrost!
},
{
cache: getCache(),
cache: {
db: getDbCache(),
healthCheck: healthCheckCache
},
cardanoNode,
db,
epochMonitor: getEpochMonitor(db),
dbPools,
epochMonitor: getEpochMonitor(dbPools.main),
genesisData,
logger,
metadataService: createHttpStakePoolMetadataService(logger)
Expand All @@ -122,36 +132,64 @@ const serviceMapFactory = (options: ServiceMapFactoryOptions) => {
return new StakePoolHttpService({ logger, stakePoolProvider });
}, ServiceNames.StakePool),
[ServiceNames.Utxo]: withDbSyncProvider(
async (db, cardanoNode) =>
new UtxoHttpService({ logger, utxoProvider: new DbSyncUtxoProvider({ cardanoNode, db, logger }) }),
async (dbPools, cardanoNode) =>
new UtxoHttpService({
logger,
utxoProvider: new DbSyncUtxoProvider({
cache: {
healthCheck: healthCheckCache
},
cardanoNode,
dbPools,
logger
})
}),
ServiceNames.Utxo
),
[ServiceNames.ChainHistory]: withDbSyncProvider(async (db, cardanoNode) => {
const metadataService = createDbSyncMetadataService(db, logger);
[ServiceNames.ChainHistory]: withDbSyncProvider(async (dbPools, cardanoNode) => {
const metadataService = createDbSyncMetadataService(dbPools.main, logger);
const chainHistoryProvider = new DbSyncChainHistoryProvider(
{ paginationPageSizeLimit: args.paginationPageSizeLimit! },
{ cardanoNode, db, logger, metadataService }
{
cache: {
healthCheck: healthCheckCache
},
cardanoNode,
dbPools,
logger,
metadataService
}
);
return new ChainHistoryHttpService({ chainHistoryProvider, logger });
}, ServiceNames.ChainHistory),
[ServiceNames.Rewards]: withDbSyncProvider(async (db, cardanoNode) => {
[ServiceNames.Rewards]: withDbSyncProvider(async (dbPools, cardanoNode) => {
const rewardsProvider = new DbSyncRewardsProvider(
{ paginationPageSizeLimit: args.paginationPageSizeLimit! },
{ cardanoNode, db, logger }
{
cache: {
healthCheck: healthCheckCache
},
cardanoNode,
dbPools,
logger
}
);
return new RewardsHttpService({ logger, rewardsProvider });
}, ServiceNames.Rewards),
[ServiceNames.NetworkInfo]: withDbSyncProvider(async (db, cardanoNode) => {
[ServiceNames.NetworkInfo]: withDbSyncProvider(async (dbPools, cardanoNode) => {
if (!genesisData)
throw new MissingProgramOption(
ServiceNames.NetworkInfo,
ProviderServerOptionDescriptions.CardanoNodeConfigPath
);
const networkInfoProvider = new DbSyncNetworkInfoProvider({
cache: getCache(),
cache: {
db: getDbCache(),
healthCheck: healthCheckCache
},
cardanoNode,
db,
epochMonitor: getEpochMonitor(db),
dbPools,
epochMonitor: getEpochMonitor(dbPools.main),
genesisData,
logger
});
Expand Down Expand Up @@ -186,12 +224,15 @@ export const loadProviderServer = async (
},
logger
);
const db = await getPool(dnsResolver, logger, args);
const pools: Partial<DbPools> = {
healthCheck: await getPool(dnsResolver, logger, args),
main: await getPool(dnsResolver, logger, args)
};
const cardanoNode = serviceSetHas(args.serviceNames, cardanoNodeDependantServices)
? await getOgmiosCardanoNode(dnsResolver, logger, args)
: undefined;
const genesisData = args.cardanoNodeConfigPath ? await loadGenesisData(args.cardanoNodeConfigPath) : undefined;
const serviceMap = serviceMapFactory({ args, dbConnection: db, dnsResolver, genesisData, logger, node: cardanoNode });
const serviceMap = serviceMapFactory({ args, dnsResolver, genesisData, logger, node: cardanoNode, pools });

for (const serviceName of args.serviceNames) {
if (serviceMap[serviceName]) {
Expand Down
2 changes: 2 additions & 0 deletions packages/cardano-services/src/Program/programs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum Programs {

/**
* Used as mount segments, so must be URL-friendly
*
*/
export enum ServiceNames {
Asset = 'asset',
Expand Down Expand Up @@ -45,6 +46,7 @@ export type ProviderServerArgs = CommonProgramOptions &
cardanoNodeConfigPath?: string;
disableDbCache?: boolean;
disableStakePoolMetricApy?: boolean;
healthCheckCacheTtl: number;
tokenMetadataCacheTTL?: number;
tokenMetadataServerUrl?: string;
tokenMetadataRequestTimeout?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ export class DbSyncRewardsProvider extends DbSyncProvider() implements RewardsPr

constructor(
{ paginationPageSizeLimit }: RewardsProviderProps,
{ db, cardanoNode, logger }: DbSyncProviderDependencies
{ cache, dbPools, cardanoNode, logger }: DbSyncProviderDependencies
) {
super({ cardanoNode, db, logger });
this.#builder = new RewardsBuilder(db, logger);
super({ cache, cardanoNode, dbPools, logger });
this.#builder = new RewardsBuilder(dbPools.main, logger);
this.#paginationPageSizeLimit = paginationPageSizeLimit;
}

Expand Down

0 comments on commit c66683f

Please sign in to comment.