diff --git a/src/autoscaler-common/types.js b/src/autoscaler-common/types.js index ab1b95d7..0f88540a 100644 --- a/src/autoscaler-common/types.js +++ b/src/autoscaler-common/types.js @@ -25,6 +25,7 @@ const AutoscalerUnits = { * currentSize: number, * regional: boolean, * currentNodes: number, + * currentNumDatabases: number, * }} SpannerMetadata */ diff --git a/src/poller/poller-core/index.js b/src/poller/poller-core/index.js index 1722d2d7..2b871111 100644 --- a/src/poller/poller-core/index.js +++ b/src/poller/poller-core/index.js @@ -272,8 +272,13 @@ async function getSpannerMetadata(projectId, spannerInstanceId, units) { try { const spannerInstance = spanner.instance(spannerInstanceId); - const data = await spannerInstance.getMetadata(); - const metadata = data[0]; + + const results = await Promise.all([ + spannerInstance.getDatabases(), + spannerInstance.getMetadata(), + ]); + const numDatabases = results[0][0].length; + const metadata = results[1][0]; logger.debug({ message: `DisplayName: ${metadata['displayName']}`, projectId: projectId, @@ -294,6 +299,11 @@ async function getSpannerMetadata(projectId, spannerInstanceId, units) { projectId: projectId, instanceId: spannerInstanceId, }); + logger.debug({ + message: `numDatabases: ${numDatabases}`, + projectId: projectId, + instanceId: spannerInstanceId, + }); /** @type {SpannerMetadata} */ const spannerMetadata = { @@ -302,6 +312,7 @@ async function getSpannerMetadata(projectId, spannerInstanceId, units) { ? assertDefined(metadata['nodeCount']) : assertDefined(metadata['processingUnits']), regional: !!metadata['config']?.split('/')?.pop()?.startsWith('regional'), + currentNumDatabases: numDatabases, // DEPRECATED currentNodes: assertDefined(metadata['nodeCount']), }; diff --git a/src/scaler/README.md b/src/scaler/README.md index 64cc2e09..ef151aa3 100644 --- a/src/scaler/README.md +++ b/src/scaler/README.md @@ -130,6 +130,7 @@ The following is an example: } ], "currentSize":100, + "currentNumDatabases": 10, "regional":true } ``` diff --git a/src/scaler/scaler-core/scaling-methods/base.js b/src/scaler/scaler-core/scaling-methods/base.js index a1828854..cfedc7ed 100644 --- a/src/scaler/scaler-core/scaling-methods/base.js +++ b/src/scaler/scaler-core/scaling-methods/base.js @@ -37,7 +37,12 @@ const RelativeToRange = { // margin const DEFAULT_THRESHOLD_MARGIN = 5; +// Min 10 databases per 100 processing units. +// https://cloud.google.com/spanner/quotas#database-limits +const DATABASES_PER_100_PU = 10; + const {logger} = require('../../../autoscaler-common/logger'); +const {AutoscalerUnits} = require('../../../autoscaler-common/types'); /** * @typedef {import('../../../autoscaler-common/types').AutoscalerSpanner @@ -170,6 +175,21 @@ function loopThroughSpannerMetrics(spanner, getSuggestedSize) { }); let maxSuggestedSize = spanner.minSize; + + if ( + spanner.units === AutoscalerUnits.PROCESSING_UNITS && + spanner.currentNumDatabases + ) { + const minUnitsForNumDatabases = + Math.ceil(spanner.currentNumDatabases / DATABASES_PER_100_PU) * 100; + logger.info({ + message: `\tMinumum ${minUnitsForNumDatabases} ${spanner.units} required for ${spanner.currentNumDatabases} databases`, + projectId: spanner.projectId, + instanceId: spanner.instanceId, + }); + maxSuggestedSize = Math.max(maxSuggestedSize, minUnitsForNumDatabases); + } + spanner.isOverloaded = false; for (const metric of /** @type {SpannerMetricValue[]} */ (spanner.metrics)) { diff --git a/src/scaler/scaler-core/test/scaling-methods/base.test.js b/src/scaler/scaler-core/test/scaling-methods/base.test.js index c2aefe2d..b21a20d3 100644 --- a/src/scaler/scaler-core/test/scaling-methods/base.test.js +++ b/src/scaler/scaler-core/test/scaling-methods/base.test.js @@ -265,4 +265,22 @@ describe('#loopThroughSpannerMetrics', () => { spanner.metrics[0].should.have.property('margin'); spanner.metrics[1].should.have.property('margin').and.equal(99); }); + + it('should clamp to a min number of PU based on currentNumDatabases', () => { + const spanner = /** @type {AutoscalerSpanner} */ ({ + units: 'PROCESSING_UNITS', + minSize: 100, + currentNumDatabases: 55, + currentSize: 1000, + maxSize: 2000, + metrics: [ + {name: 'high_priority_cpu', threshold: 65, value: 10}, + {name: 'rolling_24_hr', threshold: 90, value: 10}, + ], + }); + + // Metrics suggest 100PU, but 55 db's requires 600 PU + const suggestedSize = loopThroughSpannerMetrics(spanner, () => 100); + suggestedSize.should.be.equal(600); + }); }); diff --git a/src/scaler/scaler-core/test/state.test.js b/src/scaler/scaler-core/test/state.test.js index 60e908bd..0fa6e8cb 100644 --- a/src/scaler/scaler-core/test/state.test.js +++ b/src/scaler/scaler-core/test/state.test.js @@ -104,6 +104,7 @@ const BASE_CONFIG = { maxSize: 200, stepSize: 10, overloadStepSize: 10, + currentNumDatabases: 1, }; describe('stateFirestoreTests', () => {