Tolerate concurrent stats index creation
All checks were successful
deploy / deploy (push) Successful in 1m17s
All checks were successful
deploy / deploy (push) Successful in 1m17s
Proof: observed the deployed stats fix restart app pods once when concurrent startup index creation hit PostgreSQL pg_class_relname_nsp_index; added an idempotent schema-index helper that treats concurrent already-created index errors as success and added regression tests for the exact duplicate-index race and unrelated duplicate errors. Revalidated targeted stats/schema tests, full npm test, and operator dashboard bundle build. Assumptions: PostgreSQL error 23505 on pg_class_relname_nsp_index and 42P07 during CREATE INDEX IF NOT EXISTS mean another repo-owned process created the same schema object concurrently, so continuing is equivalent to the intended idempotent schema state. Still fake: this does not remove already-written duplicate statistic rows with malformed historical stat_id strings; the loader de-dupes them at read time, and venue-native settlement truth remains limited to existing durable evidence.
This commit is contained in:
parent
8361defa53
commit
a1e3939b79
2 changed files with 52 additions and 2 deletions
|
|
@ -440,7 +440,7 @@ export async function ensureQuoteLifecycleRetentionSchema(pool) {
|
|||
CREATE INDEX IF NOT EXISTS ${QUOTE_LIFECYCLE_STATISTIC_SUBJECTS_TABLE}_bucket_idx
|
||||
ON ${QUOTE_LIFECYCLE_STATISTIC_SUBJECTS_TABLE} (bucket, latest_stage_at DESC NULLS LAST)
|
||||
`);
|
||||
await pool.query(`
|
||||
await ensureSchemaIndex(pool, `
|
||||
CREATE INDEX IF NOT EXISTS ${QUOTE_LIFECYCLE_STATISTIC_SUBJECTS_TABLE}_updated_idx
|
||||
ON ${QUOTE_LIFECYCLE_STATISTIC_SUBJECTS_TABLE} (updated_at DESC)
|
||||
`);
|
||||
|
|
@ -6418,8 +6418,25 @@ function withDepositCreatedAt(payload, effectiveAt) {
|
|||
}
|
||||
|
||||
async function ensureExpressionIndex(pool, { name, table, expression }) {
|
||||
await pool.query(`
|
||||
await ensureSchemaIndex(pool, `
|
||||
CREATE INDEX IF NOT EXISTS ${name}
|
||||
ON ${table} (${expression})
|
||||
`);
|
||||
}
|
||||
|
||||
export async function ensureSchemaIndex(pool, sql) {
|
||||
try {
|
||||
await pool.query(sql);
|
||||
} catch (error) {
|
||||
if (isConcurrentSchemaIndexExistsError(error)) return;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function isConcurrentSchemaIndexExistsError(error) {
|
||||
return error?.code === '42P07'
|
||||
|| (
|
||||
error?.code === '23505'
|
||||
&& error?.constraint === 'pg_class_relname_nsp_index'
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
buildQuoteLifecycleStatistics,
|
||||
} from '../src/core/quote-lifecycle-statistics.mjs';
|
||||
import {
|
||||
ensureSchemaIndex,
|
||||
loadQuoteLifecycleStatistics,
|
||||
refreshQuoteLifecycleStatistics,
|
||||
} from '../src/lib/postgres.mjs';
|
||||
|
|
@ -192,3 +193,35 @@ test('postgres quote lifecycle statistics loader returns all-time and clamped gr
|
|||
assert.match(issuedSql, /PARTITION BY grain, window_start, window_end/);
|
||||
assert.match(issuedSql, /duplicate_rank = 1/);
|
||||
});
|
||||
|
||||
test('postgres schema index creation tolerates concurrent already-created index race', async () => {
|
||||
let queryCount = 0;
|
||||
const pool = {
|
||||
async query() {
|
||||
queryCount += 1;
|
||||
const error = new Error('duplicate key value violates unique constraint "pg_class_relname_nsp_index"');
|
||||
error.code = '23505';
|
||||
error.constraint = 'pg_class_relname_nsp_index';
|
||||
throw error;
|
||||
},
|
||||
};
|
||||
|
||||
await ensureSchemaIndex(pool, 'CREATE INDEX IF NOT EXISTS quote_lifecycle_stat_subjects_updated_idx ON quote_lifecycle_stat_subjects (updated_at DESC)');
|
||||
assert.equal(queryCount, 1);
|
||||
});
|
||||
|
||||
test('postgres schema index creation still rejects unrelated duplicate errors', async () => {
|
||||
const pool = {
|
||||
async query() {
|
||||
const error = new Error('duplicate key value violates unrelated constraint');
|
||||
error.code = '23505';
|
||||
error.constraint = 'other_unique_constraint';
|
||||
throw error;
|
||||
},
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
() => ensureSchemaIndex(pool, 'CREATE INDEX IF NOT EXISTS unrelated_idx ON some_table (id)'),
|
||||
/unrelated constraint/,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue