velocious 1.0.690 → 1.0.692
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/build/background-jobs/store.js +61 -0
- package/build/background-jobs/worker.js +21 -4
- package/build/configuration-types.js +1 -0
- package/build/database/drivers/base.js +54 -2
- package/build/database/drivers/mysql/index.js +23 -0
- package/build/database/drivers/mysql/query.js +1 -1
- package/build/src/background-jobs/store.d.ts +10 -0
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +55 -1
- package/build/src/background-jobs/worker.d.ts +11 -5
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +21 -5
- package/build/src/configuration-types.d.ts +5 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +2 -1
- package/build/src/database/drivers/base.d.ts +21 -0
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +52 -3
- package/build/src/database/drivers/mysql/index.d.ts +9 -0
- package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/index.js +20 -1
- package/build/src/database/drivers/mysql/query.js +2 -2
- package/package.json +1 -1
- package/src/background-jobs/store.js +61 -0
- package/src/background-jobs/worker.js +21 -4
- package/src/configuration-types.js +1 -0
- package/src/database/drivers/base.js +54 -2
- package/src/database/drivers/mysql/index.js +23 -0
- package/src/database/drivers/mysql/query.js +1 -1
|
@@ -1617,7 +1617,11 @@ export default class BackgroundJobsWorker {
|
|
|
1617
1617
|
* whole drain window, which can stretch to the longest in-flight job. The
|
|
1618
1618
|
* retiring child stops receiving new jobs and is terminated only once its
|
|
1619
1619
|
* in-flight set drains, so a long-running job (e.g. a build) is never cut
|
|
1620
|
-
* off.
|
|
1620
|
+
* off. When the pool already carries its bounded over-cap child, retiring
|
|
1621
|
+
* the last working child is deferred instead of emptying the working pool:
|
|
1622
|
+
* the child keeps serving and a later observation retries the retirement
|
|
1623
|
+
* once a draining exit has freed a cap slot, so advertised capacity cannot
|
|
1624
|
+
* collapse to zero inside a wave.
|
|
1621
1625
|
* @param {import("node:child_process").ChildProcess} child - Child to retire.
|
|
1622
1626
|
* @returns {void}
|
|
1623
1627
|
*/
|
|
@@ -1625,6 +1629,17 @@ export default class BackgroundJobsWorker {
|
|
|
1625
1629
|
const state = this.pooledChildStates.get(child)
|
|
1626
1630
|
if (!state || state.retiring) return
|
|
1627
1631
|
|
|
1632
|
+
// Retiring the last non-retiring child while the pool already carries its
|
|
1633
|
+
// bounded over-cap child would leave advertised capacity at zero until the
|
|
1634
|
+
// next draining exit — up to the longest in-flight job (tens of minutes
|
|
1635
|
+
// with builds), stalling dispatch cluster-wide. Defer this retirement
|
|
1636
|
+
// instead: the child keeps serving and a later observation retries once a
|
|
1637
|
+
// drain has freed a cap slot for its replacement.
|
|
1638
|
+
if (!this.shouldStop && this.configuration && this._nonRetiringPooledChildCount() <= 1 && this.pooledChildren.size > this.pooledRunnerCount) {
|
|
1639
|
+
this._logPooledCapacityEvent("pooled-capacity-retirement-deferred")
|
|
1640
|
+
return
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1628
1643
|
state.retiring = true
|
|
1629
1644
|
// Best-effort pre-warm: skip when stopping (no new work) or before the
|
|
1630
1645
|
// worker is initialized (no configuration to fork a child from). The cap
|
|
@@ -1688,9 +1703,11 @@ export default class BackgroundJobsWorker {
|
|
|
1688
1703
|
* surface that reaches the production log). Used by the bounded liveness
|
|
1689
1704
|
* replacement in `_ensureNonRetiringChild`: the spawn that keeps dispatch
|
|
1690
1705
|
* alive through a full retirement wave (at a retirement mark or a child
|
|
1691
|
-
* exit),
|
|
1692
|
-
*
|
|
1693
|
-
*
|
|
1706
|
+
* exit), the deferred retirement of the last working child while the pool
|
|
1707
|
+
* already carries its bounded over-cap child (retried after the next drain
|
|
1708
|
+
* frees a cap slot), or the bounded starvation when an over-cap child is
|
|
1709
|
+
* already draining and the pool must wait for an exit.
|
|
1710
|
+
* @param {"pooled-capacity-liveness-spawn" | "pooled-capacity-starved" | "pooled-capacity-retirement-deferred"} event - Event name.
|
|
1694
1711
|
* @returns {void}
|
|
1695
1712
|
*/
|
|
1696
1713
|
_logPooledCapacityEvent(event) {
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
* @property {boolean} [readOnly] - Whether writes should be blocked for this database.
|
|
94
94
|
* @property {string} [schema] - Default schema for unqualified table lookups (MSSQL).
|
|
95
95
|
* @property {boolean} [schemaCache] - Whether schema metadata should be cached on the driver. Defaults to true.
|
|
96
|
+
* @property {number} [schemaMetadataRetryDeadlineMs] - Bounded deadline (ms) for outlasting a cached schema-metadata read the server skipped because concurrent online DDL is modifying the table. Defaults to 60000.
|
|
96
97
|
* @property {object} [record] - Record-level configuration.
|
|
97
98
|
* @property {boolean} [record.transactions] - Whether record operations should use transactions.
|
|
98
99
|
* @property {boolean} [reset] - Whether to reset the database on startup.
|
|
@@ -183,6 +183,12 @@ const SCHEMA_INVALIDATION_SCAN_LIMIT = 8192
|
|
|
183
183
|
/** Maximum checkout-name characters inspected by retry diagnostics. */
|
|
184
184
|
const OPERATION_NAME_SCAN_LIMIT = 1024
|
|
185
185
|
const REDACTED_DIAGNOSTIC_LABEL = "[REDACTED]"
|
|
186
|
+
/** First backoff between retries of a schema-metadata read blocked by concurrent online DDL. */
|
|
187
|
+
const SCHEMA_METADATA_RETRY_INITIAL_DELAY_MS = 250
|
|
188
|
+
/** Largest backoff between retries of a schema-metadata read blocked by concurrent online DDL. */
|
|
189
|
+
const SCHEMA_METADATA_RETRY_MAX_DELAY_MS = 2000
|
|
190
|
+
/** Default bounded deadline for outlasting a concurrent online-DDL metadata skip. */
|
|
191
|
+
const SCHEMA_METADATA_RETRY_DEADLINE_MS = 60000
|
|
186
192
|
|
|
187
193
|
/**
|
|
188
194
|
* Builds a non-reversible, stable SQL fingerprint without retaining SQL text. Literal spelling is
|
|
@@ -777,6 +783,52 @@ export default class VelociousDatabaseDriversBase {
|
|
|
777
783
|
return this.getArgs().schemaCache !== false
|
|
778
784
|
}
|
|
779
785
|
|
|
786
|
+
/**
|
|
787
|
+
* Runs schema metadata retry deadline ms.
|
|
788
|
+
* @returns {number} - Bounded deadline for outlasting transient schema-metadata contention.
|
|
789
|
+
*/
|
|
790
|
+
_schemaMetadataRetryDeadlineMs() {
|
|
791
|
+
const configured = this.getArgs().schemaMetadataRetryDeadlineMs
|
|
792
|
+
|
|
793
|
+
return typeof configured === "number" ? configured : SCHEMA_METADATA_RETRY_DEADLINE_MS
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Whether an error raised by a schema-metadata read is transient contention that a bounded
|
|
798
|
+
* retry can outlast. Drivers override this for their own contention signatures; the default
|
|
799
|
+
* treats every failure as final so only drivers that prove the transient shape retry it.
|
|
800
|
+
* @param {unknown} _error - Error raised by a schema-metadata read.
|
|
801
|
+
* @returns {boolean} - Whether the read may be retried.
|
|
802
|
+
*/
|
|
803
|
+
_isTransientSchemaMetadataError(_error) {
|
|
804
|
+
return false
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Runs a schema-metadata read, retrying transient concurrent-DDL contention until the bounded
|
|
809
|
+
* deadline. Non-transient failures and exhausted deadlines propagate the original error.
|
|
810
|
+
* @template T
|
|
811
|
+
* @param {() => Promise<T>} callback - Cache-miss schema read.
|
|
812
|
+
* @returns {Promise<T>} - Resolves with the read result.
|
|
813
|
+
*/
|
|
814
|
+
async _withSchemaMetadataDdlRetry(callback) {
|
|
815
|
+
const deadlineMs = this._schemaMetadataRetryDeadlineMs()
|
|
816
|
+
const startedAt = Date.now()
|
|
817
|
+
let delayMs = SCHEMA_METADATA_RETRY_INITIAL_DELAY_MS
|
|
818
|
+
|
|
819
|
+
for (;;) {
|
|
820
|
+
try {
|
|
821
|
+
return await callback()
|
|
822
|
+
} catch (error) {
|
|
823
|
+
if (!this._isTransientSchemaMetadataError(error)) throw error
|
|
824
|
+
if (Date.now() - startedAt + delayMs > deadlineMs) throw error
|
|
825
|
+
|
|
826
|
+
await wait(delayMs)
|
|
827
|
+
delayMs = Math.min(delayMs * 2, SCHEMA_METADATA_RETRY_MAX_DELAY_MS)
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
780
832
|
/**
|
|
781
833
|
* Runs cached schema metadata.
|
|
782
834
|
* @template T
|
|
@@ -785,7 +837,7 @@ export default class VelociousDatabaseDriversBase {
|
|
|
785
837
|
* @returns {Promise<T>} - Resolves with the cached metadata.
|
|
786
838
|
*/
|
|
787
839
|
async _cachedSchemaMetadata(cacheKey, callback) {
|
|
788
|
-
if (!this._schemaCacheEnabled()) return await callback
|
|
840
|
+
if (!this._schemaCacheEnabled()) return await this._withSchemaMetadataDdlRetry(callback)
|
|
789
841
|
|
|
790
842
|
const existingPromise = this._schemaCache.get(cacheKey)
|
|
791
843
|
|
|
@@ -793,7 +845,7 @@ export default class VelociousDatabaseDriversBase {
|
|
|
793
845
|
return /** @type {T} */ (this._schemaCacheReturnValue(await existingPromise))
|
|
794
846
|
}
|
|
795
847
|
|
|
796
|
-
const promise = (async () => await callback
|
|
848
|
+
const promise = (async () => await this._withSchemaMetadataDdlRetry(callback))()
|
|
797
849
|
|
|
798
850
|
this._schemaCache.set(cacheKey, promise)
|
|
799
851
|
|
|
@@ -723,6 +723,29 @@ export default class VelociousDatabaseDriversMysql extends Base{
|
|
|
723
723
|
return insert.toSql()
|
|
724
724
|
}
|
|
725
725
|
|
|
726
|
+
/**
|
|
727
|
+
* MariaDB skips information-schema/SHOW reads for a table whose definition is being modified
|
|
728
|
+
* by concurrent online DDL (ER_WARN_I_S_SKIPPED_TABLE). The build completes in seconds, so the
|
|
729
|
+
* skip is transient contention that the bounded schema-metadata retry can outlast. Query
|
|
730
|
+
* helpers wrap the driver error, so the code is searched through the cause chain.
|
|
731
|
+
* @param {unknown} error - Error raised by a schema-metadata read.
|
|
732
|
+
* @returns {boolean} - Whether the read hit concurrent-DDL metadata contention.
|
|
733
|
+
*/
|
|
734
|
+
_isTransientSchemaMetadataError(error) {
|
|
735
|
+
let currentError = /** @type {{code?: string, cause?: unknown} | undefined} */ (
|
|
736
|
+
/** @type {unknown} */ (error)
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
for (let depth = 0; depth < 5 && currentError; depth++) {
|
|
740
|
+
if (currentError.code === "ER_WARN_I_S_SKIPPED_TABLE") return true
|
|
741
|
+
currentError = /** @type {{code?: string, cause?: unknown} | undefined} */ (
|
|
742
|
+
/** @type {unknown} */ (currentError.cause)
|
|
743
|
+
)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
return false
|
|
747
|
+
}
|
|
748
|
+
|
|
726
749
|
/**
|
|
727
750
|
* Runs get tables.
|
|
728
751
|
* @returns {Promise<Array<import("../base-table.js").default>>} - Resolves with the tables.
|
|
@@ -171,7 +171,7 @@ export default async function query(pool, sql, {signal} = {}) {
|
|
|
171
171
|
connection.release()
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
reject(new Error(`Query failed because of ${error}: ${sql}
|
|
174
|
+
reject(new Error(`Query failed because of ${error}: ${sql}`, {cause: error}))
|
|
175
175
|
|
|
176
176
|
return
|
|
177
177
|
}
|