Skip to content

Commit

Permalink
feat: replace DB polling with postgres notification listener for migr…
Browse files Browse the repository at this point in the history
…ations

- Adds HasuraClient to encapsulate behaviour, keeping the Db free from
Hasura concerns.
- Moves Cardano DB Hasura config into package
- Adds config to nixos service
- cardano-db-sync now considers EBBs as part of the epoch blocksCount,
so test assertions updated
  • Loading branch information
rhyslbw committed Aug 11, 2020
1 parent 2915b02 commit e42eb3d
Show file tree
Hide file tree
Showing 51 changed files with 1,608 additions and 214 deletions.
1 change: 0 additions & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ pipeline {
stage('Test') {
steps {
sh "CARDANO_GRAPHQL_VERSION=${env.GIT_COMMIT} yarn mainnet:stack"
sh "CARDANO_GRAPHQL_VERSION=${env.GIT_COMMIT} yarn mainnet_candidate_4:stack"
sh 'sleep 15'
sh 'TEST_MODE=e2e yarn workspaces run test --ci'
}
Expand Down
11 changes: 9 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ services:
- POSTGRES_DB_FILE=/run/secrets/postgres_db
- POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password
- POSTGRES_USER_FILE=/run/secrets/postgres_user
ports:
- ${POSTGRES_PORT:-5432}:5432
secrets:
- postgres_db
- postgres_password
Expand All @@ -22,7 +24,7 @@ services:
max-size: "200k"
max-file: "10"
cardano-node:
image: inputoutput/cardano-node:${CARDANO_NODE_VERSION:-1.18.0}
image: rhyslbw/cardano-node:${CARDANO_NODE_VERSION:-a4b6dae699fa21dc3c025c8a83d1718475cb3afc}
command: [
"run",
"--config", "/config/config.json",
Expand All @@ -42,7 +44,7 @@ services:
max-size: "400k"
max-file: "20"
cardano-db-sync-extended:
image: inputoutput/cardano-db-sync:${CARDANO_DB_SYNC_VERSION:-3.1.0}
image: inputoutput/cardano-db-sync:${CARDANO_DB_SYNC_VERSION:-36e7621fa5ecb4be923569f735b74d60a26da6c1}
command: [
"--config", "/config/config.json",
"--socket-path", "/node-ipc/node.socket"
Expand Down Expand Up @@ -101,6 +103,7 @@ services:
target: server
image: inputoutput/cardano-graphql:${CARDANO_GRAPHQL_VERSION:-2.0.0}
environment:
- ALLOW_INTROSPECTION=true
- CACHE_ENABLED=true
- GENESIS_FILE_BYRON=/genesis/byron.json
- GENESIS_FILE_SHELLEY=/genesis/shelley.json
Expand All @@ -115,6 +118,10 @@ services:
ports:
- ${API_PORT:-3100}:3100
restart: on-failure
secrets:
- postgres_db
- postgres_password
- postgres_user
logging:
driver: "json-file"
options:
Expand Down
20 changes: 19 additions & 1 deletion nix/nixos/cardano-graphql-service.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ in {
services.cardano-graphql = {
enable = lib.mkEnableOption "cardano-explorer graphql service";

dbHost = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
};

dbPassword = lib.mkOption {
type = lib.types.str;
};

dbPort = lib.mkOption {
type = lib.types.int;
default = 5432;
};

dbUser = lib.mkOption {
type = lib.types.str;
default = "cexplorer";
Expand Down Expand Up @@ -116,8 +130,12 @@ in {
POOL_METADATA_PROXY = cfg.smashUrl;
GENESIS_FILE_BYRON = cfg.genesisByron;
GENESIS_FILE_SHELLEY = cfg.genesisShelley;

HASURA_URI = hasuraBaseUri;
POSTGRES_DB = cfg.db;
POSTGRES_HOST = cfg.dbHost;
POSTGRES_PASSWORD = cfg.dbPassword;
POSTGRES_PORT = cfg.dbPort;
POSTGRES_USER = cfg.dbUser;
PROMETHEUS_METRICS = boolToNodeJSEnv cfg.enablePrometheus;
TRACING = boolToNodeJSEnv (cfg.enableTracing || cfg.enablePrometheus);
ALLOW_INTROSPECTION = boolToNodeJSEnv cfg.allowIntrospection;
Expand Down
18 changes: 7 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,16 @@
"cleanup": "yarn workspaces run cleanup && shx rm -rf node_modules",
"cli:install": "yarn workspace @cardano-graphql/cli global:add",
"cli:uninstall": "yarn workspace @cardano-graphql/cli global:remove",
"mainnet:stack": "API_PORT=3100 HASURA_PORT=8090 NETWORK=mainnet docker-compose -p mainnet up --build -d",
"mainnet:stack": "API_PORT=3100 HASURA_PORT=8090 NETWORK=mainnet POSTGRES_PORT=5442 docker-compose -p mainnet up --build -d",
"mainnet:up": "yarn mainnet:stack && docker-compose -p mainnet logs -f",
"mainnet:dev": "API_PORT=3100 HASURA_PORT=8090 NETWORK=mainnet yarn service-dependencies -p mainnet up --build",
"mainnet:dev": "API_PORT=3100 HASURA_PORT=8090 NETWORK=mainnet POSTGRES_PORT=5442 yarn service-dependencies -p mainnet up --build",
"mainnet:down": "docker-compose -p mainnet down",
"mainnet:server": "API_PORT=3100 GENESIS_FILE_BYRON=${PWD}/config/network/mainnet/genesis/byron.json GENESIS_FILE_SHELLEY=${PWD}/config/network/mainnet/genesis/shelley.json HASURA_URI=http://localhost:8090 yarn workspace @cardano-graphql/server start",
"testnet:stack": "API_PORT=3101 HASURA_PORT=8091 NETWORK=testnet docker-compose -p testnet up --build -d",
"mainnet:server": "ALLOW_INTROSPECTION=true API_PORT=3100 GENESIS_FILE_BYRON=${PWD}/config/network/mainnet/genesis/byron.json GENESIS_FILE_SHELLEY=${PWD}/config/network/mainnet/genesis/shelley.json HASURA_URI=http://localhost:8090 POSTGRES_PORT=5442 yarn workspace @cardano-graphql/server start",
"testnet:stack": "API_PORT=3101 HASURA_PORT=8091 NETWORK=testnet POSTGRES_PORT=5443 docker-compose -p testnet up --build -d",
"testnet:up": "yarn testnet:stack && docker-compose -p testnet logs -f",
"testnet:dev": "API_PORT=3101 HASURA_PORT=8091 NETWORK=testnet yarn service-dependencies -p testnet up --build",
"testnet:dev": "API_PORT=3101 HASURA_PORT=8091 NETWORK=testnet POSTGRES_PORT=5443 yarn service-dependencies -p testnet up --build",
"testnet:down": "docker-compose -p testnet down",
"testnet:server": "API_PORT=3101 GENESIS_FILE_BYRON=${PWD}/config/network/testnet/genesis/byron.json GENESIS_FILE_SHELLEY=${PWD}/config/network/testnet/genesis/shelley.json HASURA_URI=http://localhost:8091 yarn workspace @cardano-graphql/server start",
"mainnet_candidate_4:stack": "API_PORT=3102 HASURA_PORT=8092 NETWORK=mainnet_candidate_4 docker-compose -p mainnet_candidate_4 up --build -d",
"mainnet_candidate_4:up": "yarn mainnet_candidate_4:stack && docker-compose -p mainnet_candidate_4 logs -f",
"mainnet_candidate_4:dev": "API_PORT=3102 HASURA_PORT=8092 NETWORK=mainnet_candidate_4 yarn service-dependencies -p mainnet_candidate_4 up --build",
"mainnet_candidate_4:down": "docker-compose -p mainnet_candidate_4 down",
"mainnet_candidate_4:server": "API_PORT=3102 GENESIS_FILE_BYRON=${PWD}/config/network/mainnet_candidate_4/genesis/byron.json GENESIS_FILE_SHELLEY=${PWD}/config/network/mainnet_candidate_4/genesis/shelley.json HASURA_URI=http://localhost:8092 yarn workspace @cardano-graphql/server start",
"testnet:server": "ALLOW_INTROSPECTION=true API_PORT=3101 GENESIS_FILE_BYRON=${PWD}/config/network/testnet/genesis/byron.json GENESIS_FILE_SHELLEY=${PWD}/config/network/testnet/genesis/shelley.json HASURA_URI=http://localhost:8091 POSTGRES_PORT=5443 yarn workspace @cardano-graphql/server start",
"lint": "yarn workspaces run lint",
"loadtest:byron-staging": "artillery run test/loadtest/byron-staging-config.yml",
"publish-packages": "yarn workspaces run publish",
Expand Down Expand Up @@ -58,6 +53,7 @@
"@types/lodash.set": "^4.3.6",
"@types/node": "^14.0.13",
"@types/node-fetch": "^2.5.7",
"@types/pg": "^7.14.4",
"@types/set-interval-async": "^1.0.0",
"@types/tmp": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^3.2.0",
Expand Down
Binary file removed packages-cache/@babel-runtime-7.5.0.tgz
Binary file not shown.
Binary file added packages-cache/@types-pg-7.14.4.tgz
Binary file not shown.
Binary file added packages-cache/@types-pg-types-1.11.5.tgz
Binary file not shown.
Binary file added packages-cache/buffer-writer-2.0.0.tgz
Binary file not shown.
Binary file added packages-cache/packet-reader-1.0.0.tgz
Binary file not shown.
Binary file added packages-cache/parse-json-4.0.0.tgz
Binary file not shown.
Binary file added packages-cache/pg-8.3.0.tgz
Binary file not shown.
Binary file added packages-cache/pg-connection-string-2.3.0.tgz
Binary file not shown.
Binary file added packages-cache/pg-format-1.0.4.tgz
Binary file not shown.
Binary file added packages-cache/pg-int8-1.0.1.tgz
Binary file not shown.
Binary file added packages-cache/pg-listen-1.6.0.tgz
Binary file not shown.
Binary file added packages-cache/pg-pool-3.2.1.tgz
Binary file not shown.
Binary file added packages-cache/pg-protocol-1.2.5.tgz
Binary file not shown.
Binary file added packages-cache/pg-types-2.2.0.tgz
Binary file not shown.
Binary file added packages-cache/pgpass-1.0.2.tgz
Binary file not shown.
Binary file added packages-cache/postgres-array-2.0.0.tgz
Binary file not shown.
Binary file added packages-cache/postgres-bytea-1.0.0.tgz
Binary file not shown.
Binary file added packages-cache/postgres-date-1.0.5.tgz
Binary file not shown.
Binary file added packages-cache/postgres-interval-1.2.0.tgz
Binary file not shown.
Binary file added packages-cache/semver-4.3.2.tgz
Binary file not shown.
Binary file removed packages-cache/set-interval-async-1.0.33.tgz
Binary file not shown.
Binary file added packages-cache/split-1.0.1.tgz
Binary file not shown.
Binary file added packages-cache/typed-emitter-0.1.0.tgz
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ CREATE VIEW "Delegation" AS
SELECT
delegation.id AS "id",
(
SELECT stake_address.hash
SELECT stake_address.view
FROM stake_address
WHERE stake_address.id = delegation.addr_id
) AS "address",
Expand Down Expand Up @@ -63,7 +63,7 @@ SELECT
reward.amount AS "amount",
reward.id AS "id",
(
SELECT stake_address.hash
SELECT stake_address.view
FROM stake_address
WHERE stake_address.id = reward.addr_id
) AS "address",
Expand All @@ -82,7 +82,7 @@ CREATE VIEW "StakeDeregistration" AS
SELECT
stake_deregistration.id AS "id",
(
SELECT stake_address.hash
SELECT stake_address.view
FROM stake_address
WHERE stake_address.id = stake_deregistration.addr_id
) AS "address",
Expand All @@ -107,7 +107,7 @@ SELECT
block.block_no AS "blockNo",
pool.registered_tx_id AS "updated_in_tx_id",
pool.pledge AS "pledge",
( SELECT stake_address.hash FROM stake_address WHERE stake_address.id = pool.reward_addr_id) AS "rewardAddress",
( SELECT stake_address.view FROM stake_address WHERE stake_address.id = pool.reward_addr_id) AS "rewardAddress",
pool_meta_data.url AS "url"
FROM pool_update AS pool
INNER JOIN pool_meta_data ON pool.meta = pool_meta_data.id
Expand All @@ -119,7 +119,7 @@ CREATE VIEW "StakeRegistration" AS
SELECT
stake_registration.id AS "id",
(
SELECT stake_address.hash
SELECT stake_address.view
FROM stake_address
WHERE stake_address.id = stake_registration.addr_id
) AS "address",
Expand Down Expand Up @@ -186,7 +186,7 @@ SELECT
withdrawal.amount AS "amount",
withdrawal.id AS "id",
(
SELECT stake_address.hash
SELECT stake_address.view
FROM stake_address
WHERE stake_address.id = withdrawal.addr_id
) AS "address",
Expand Down
3 changes: 2 additions & 1 deletion packages/api-cardano-db-hasura/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"graphql-bigint": "^1.0.0",
"graphql-scalars": "^1.2.1",
"p-retry": "^4.2.0",
"set-interval-async": "^1.0.33"
"pg": "^8.3.0",
"pg-listen": "^1.6.0"
},
"devDependencies": {
"@cardano-graphql/util-dev": "2.0.0",
Expand Down
11 changes: 11 additions & 0 deletions packages/api-cardano-db-hasura/src/Config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

export interface Config {
db: {
database: string,
host: string,
password: string,
port: number
user: string,
},
hasuraUri: string
}
137 changes: 36 additions & 101 deletions packages/api-cardano-db-hasura/src/Db.ts
Original file line number Diff line number Diff line change
@@ -1,113 +1,48 @@
import { ApolloClient, gql, InMemoryCache, NormalizedCacheObject } from 'apollo-boost'
import { createHttpLink } from 'apollo-link-http'
import fetch from 'cross-fetch'
import utc from 'dayjs/plugin/utc'
import dayjs from 'dayjs'
import { exec } from 'child_process'
import { clearIntervalAsync, setIntervalAsync, SetIntervalAsyncTimer } from 'set-interval-async/dynamic'
import path from 'path'
import pRetry from 'p-retry'
import util from '@cardano-graphql/util'

dayjs.extend(utc)
import { ClientConfig } from 'pg'
import createSubscriber, { Subscriber } from 'pg-listen'

export class Db {
hasuraClient: ApolloClient<NormalizedCacheObject>
hasuraUri: string
monitorTimer: SetIntervalAsyncTimer
pgSubscriber: Subscriber

constructor (hasuraUri: string) {
this.hasuraUri = hasuraUri
this.hasuraClient = new ApolloClient({
cache: new InMemoryCache({
addTypename: false
}),
defaultOptions: {
query: {
fetchPolicy: 'network-only'
}
},
link: createHttpLink({
uri: `${this.hasuraUri}/v1/graphql`,
fetch,
headers: {
'X-Hasura-Role': 'cardano-graphql'
}
})
constructor (pgClientConfig: ClientConfig) {
this.pgSubscriber = createSubscriber(pgClientConfig, {
parse: (value) => value
})
}

public async init (): Promise<void> {
// Todo: optimal solution dependent on https://github.com/input-output-hk/cardano-db-sync/issues/182
await this.applySchemaAndMetadata()
this.monitorDbState()
}

public async shutdown () {
await clearIntervalAsync(this.monitorTimer)
}

private monitorDbState () {
this.monitorTimer = setIntervalAsync(
async () => {
try {
await this.getMeta()
} catch (error) {
if (error.message === 'GraphQL error: field "cardano" not found in type: \'query_root\'' || error.message === 'GraphQL error: database query error') {
console.warn('Re-applying PostgreSQL migrations and Hasura metadata')
await this.applySchemaAndMetadata()
} else {
console.error(error)
}
}
},
10000
)
}

async getMeta () {
const result = await this.hasuraClient.query({
query: gql`query {
cardano {
tip {
forgedAt
}
}}`
public async init ({ onDbSetup }: { onDbSetup: Function }): Promise<void> {
this.pgSubscriber.events.on('connected', async () => {
console.log('DbClient.pgSubscriber: Connected')
await onDbSetup()
})
const { tip } = result.data?.cardano[0]
const currentUtc = dayjs().utc()
const tipUtc = dayjs.utc(tip.forgedAt)
return {
initialized: tipUtc.isAfter(currentUtc.subtract(120, 'second')),
syncPercentage: (tipUtc.valueOf() / currentUtc.valueOf()) * 100
}
}

public async applySchemaAndMetadata () {
await pRetry(async () => {
await this.hasuraCli('migrate apply --down all')
await this.hasuraCli('migrate apply --up all')
await this.hasuraCli('metadata clear')
await this.hasuraCli('metadata apply')
}, {
factor: 1.75,
retries: 9,
onFailedAttempt: util.onFailedAttemptFor('Applying PostgreSQL schema and Hasura metadata')
this.pgSubscriber.events.on('reconnect', (attempt) => {
console.warn(`DbClient.pgSubscriber: Reconnecting attempt ${attempt}`)
})
this.pgSubscriber.events.on('error', (error) => {
console.error('DbClient.pgSubscriber: Fatal database connection error:', error)
process.exit(1)
})
this.pgSubscriber.notifications.on('cardano_db_sync_startup', async payload => {
switch (payload) {
case 'init' :
console.log('DbClient.pgSubscriber: cardano-db-sync-extended starting, schema will be reset')
break
case 'db-setup' :
await onDbSetup()
break
default :
console.error(`DbClient.pgSubscriber: Unknown message payload ${payload}`)
}
})
try {
await this.pgSubscriber.connect()
await this.pgSubscriber.listenTo('cardano_db_sync_startup')
} catch (error) {
console.error(error)
}
}

private hasuraCli (command: string) {
return new Promise((resolve, reject) => {
exec(
`hasura --skip-update-check --project ${path.resolve(__dirname, '..', 'hasura', 'project')} --endpoint ${this.hasuraUri} ${command}`,
(error, stdout) => {
if (error) {
reject(error)
}
console.log(stdout)
resolve()
}
)
})
public async shutdown () {
await this.pgSubscriber.close()
}
}
Loading

0 comments on commit e42eb3d

Please sign in to comment.