Files
speckle-server/packages/server/logging/highFrequencyMetrics/knexConnectionPool.ts
T
Iain Sproat 8197bb74a3 feat(multi-region): metrics for knex for all regional databases (#3580)
* feat(multi-region): metrics for knex for all regional databases

* improve typing in knex monitoring

* error logging around migrations

* await async calls for db connections
- add 'region' label

* add missing 'await' statements

* more missing 'await'

* guard against re-adding listeners

* It was possible for update to be called before initialize
- this change collapses both into initialize, and adds checks to ensure initialization is done before being updated for new regions

* separate back into non-exported const and rename

* align with main

* Amend order at which metrics is enabled
2024-12-12 11:03:25 +01:00

116 lines
4.0 KiB
TypeScript

import { Histogram, Registry } from 'prom-client'
import type { Metric } from '@/logging/highFrequencyMetrics/highfrequencyMonitoring'
import type { Knex } from 'knex'
import { numberOfFreeConnections } from '@/modules/shared/helpers/dbHelper'
const KNEX_CONNECTIONS_FREE = 'knex_connections_free_high_frequency'
const KNEX_CONNECTIONS_USED = 'knex_connections_used_high_frequency'
const KNEX_PENDING_ACQUIRES = 'knex_pending_acquires_high_frequency'
const KNEX_PENDING_CREATES = 'knex_pending_creates_high_frequency'
const KNEX_PENDING_VALIDATIONS = 'knex_pending_validations_high_frequency'
const KNEX_REMAINING_CAPACITY = 'knex_remaining_capacity_high_frequency'
type BucketName =
| typeof KNEX_CONNECTIONS_FREE
| typeof KNEX_CONNECTIONS_USED
| typeof KNEX_PENDING_ACQUIRES
| typeof KNEX_PENDING_CREATES
| typeof KNEX_PENDING_VALIDATIONS
| typeof KNEX_REMAINING_CAPACITY
const DEFAULT_KNEX_TOTAL_BUCKETS = {
KNEX_CONNECTIONS_FREE: [0, 1, 2, 5, 10, 20],
KNEX_CONNECTIONS_USED: [0, 1, 2, 5, 10, 20],
KNEX_PENDING_ACQUIRES: [0, 1, 2, 5, 10, 20],
KNEX_PENDING_CREATES: [0, 1, 2, 5, 10, 20],
KNEX_PENDING_VALIDATIONS: [0, 1, 2, 5, 10, 20],
KNEX_REMAINING_CAPACITY: [0, 1, 2, 5, 10, 20]
}
type MetricConfig = {
prefix?: string
labels?: Record<string, string>
buckets?: Record<BucketName, number[]>
getDbClients: () => Promise<
Array<{ client: Knex; isMain: boolean; regionKey: string }>
>
}
export const knexConnections = (registry: Registry, config: MetricConfig): Metric => {
const registers = registry ? [registry] : undefined
const namePrefix = config.prefix ?? ''
const labels = config.labels ?? {}
const labelNames = [...Object.keys(labels), 'region']
const buckets = { ...DEFAULT_KNEX_TOTAL_BUCKETS, ...config.buckets }
const knexConnectionsFree = new Histogram({
name: namePrefix + KNEX_CONNECTIONS_FREE,
help: 'Number of free DB connections. This data is collected at a higher frequency than Prometheus scrapes, and is presented as a Histogram.',
registers,
buckets: buckets.KNEX_CONNECTIONS_FREE,
labelNames
})
const knexConnectionsUsed = new Histogram({
name: namePrefix + KNEX_CONNECTIONS_USED,
help: 'Number of used DB connections',
registers,
buckets: buckets.KNEX_CONNECTIONS_USED,
labelNames
})
const knexPendingAcquires = new Histogram({
name: namePrefix + KNEX_PENDING_ACQUIRES,
help: 'Number of pending DB connection aquires',
registers,
buckets: buckets.KNEX_PENDING_ACQUIRES,
labelNames
})
const knexPendingCreates = new Histogram({
name: namePrefix + KNEX_PENDING_CREATES,
help: 'Number of pending DB connection creates',
registers,
buckets: buckets.KNEX_PENDING_CREATES,
labelNames
})
const knexPendingValidations = new Histogram({
name: namePrefix + KNEX_PENDING_VALIDATIONS,
help: 'Number of pending DB connection validations. This is a state between pending acquisition and acquiring a connection.',
registers,
buckets: buckets.KNEX_PENDING_VALIDATIONS,
labelNames
})
const knexRemainingCapacity = new Histogram({
name: namePrefix + KNEX_REMAINING_CAPACITY,
help: 'Remaining capacity of the DB connection pool',
registers,
buckets: buckets.KNEX_REMAINING_CAPACITY,
labelNames
})
return {
collect: async () => {
for (const dbClient of await config.getDbClients()) {
const labelsAndRegion = { ...labels, region: dbClient.regionKey }
const connPool = dbClient.client.client.pool
knexConnectionsFree.observe(labelsAndRegion, connPool.numFree())
knexConnectionsUsed.observe(labelsAndRegion, connPool.numUsed())
knexPendingAcquires.observe(labelsAndRegion, connPool.numPendingAcquires())
knexPendingCreates.observe(labelsAndRegion, connPool.numPendingCreates())
knexPendingValidations.observe(
labelsAndRegion,
connPool.numPendingValidations()
)
knexRemainingCapacity.observe(
labelsAndRegion,
numberOfFreeConnections(dbClient.client)
)
}
}
}
}