역할별 PostgreSQL pool 상한과 동일 process 공유 pool을 적용하고 health 지표를 노출한다.\n\n게임 기능 advisory lock을 schema namespace로 통일하고 실제 PostgreSQL 통합 하네스를 보강한다.
76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
import { GamePrisma, type GamePrismaClient } from './gamePrisma.js';
|
|
import { acquireGameSchemaAdvisoryXactLock } from './gameSchemaAdvisoryLock.js';
|
|
|
|
export const READ_MODEL_REVISION_COVERAGE_VERSION = 1;
|
|
|
|
type CoverageDatabase = Pick<GamePrismaClient, '$executeRaw' | '$queryRaw'>;
|
|
|
|
interface CoverageRow {
|
|
coverageVersion: number;
|
|
}
|
|
|
|
export interface ReadModelCoverageActivationResult {
|
|
previousVersion: number;
|
|
coverageVersion: number;
|
|
seededHeads: number;
|
|
}
|
|
|
|
/**
|
|
* Transaction-bound post-deploy activation. The caller must run this only after
|
|
* every writer for this binary version is deployed. It seeds shared heads
|
|
* without overwriting concurrent increments, then raises the authority gate.
|
|
*/
|
|
export const activateReadModelRevisionCoverage = async (
|
|
transaction: CoverageDatabase,
|
|
expectedVersion = 0
|
|
): Promise<ReadModelCoverageActivationResult> => {
|
|
if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 0) {
|
|
throw new RangeError('Expected read-model coverage version must be a non-negative safe integer.');
|
|
}
|
|
|
|
await acquireGameSchemaAdvisoryXactLock(
|
|
transaction,
|
|
`read-model-revision-coverage:${READ_MODEL_REVISION_COVERAGE_VERSION}`
|
|
);
|
|
const rows = await transaction.$queryRaw<CoverageRow[]>(GamePrisma.sql`
|
|
SELECT "coverage_version" AS "coverageVersion"
|
|
FROM "read_model_revision_meta"
|
|
WHERE "id" = 1
|
|
FOR UPDATE
|
|
`);
|
|
const current = rows.length === 1 ? rows[0]?.coverageVersion : undefined;
|
|
if (
|
|
!Number.isSafeInteger(current) ||
|
|
(current !== expectedVersion && current !== READ_MODEL_REVISION_COVERAGE_VERSION)
|
|
) {
|
|
throw new Error(
|
|
`Read-model coverage activation expected ${expectedVersion} or ${READ_MODEL_REVISION_COVERAGE_VERSION}, received ${String(current)}.`
|
|
);
|
|
}
|
|
|
|
const seededHeads = await transaction.$executeRaw(GamePrisma.sql`
|
|
INSERT INTO "read_model_revision" ("domain", "entity_id", "revision", "updated_at")
|
|
VALUES
|
|
('dashboard.global', 0, 1, CURRENT_TIMESTAMP),
|
|
('map.world', 0, 1, CURRENT_TIMESTAMP)
|
|
ON CONFLICT ("domain", "entity_id") DO NOTHING
|
|
`);
|
|
if (current !== READ_MODEL_REVISION_COVERAGE_VERSION) {
|
|
const updated = await transaction.$executeRaw(GamePrisma.sql`
|
|
UPDATE "read_model_revision_meta"
|
|
SET "coverage_version" = ${READ_MODEL_REVISION_COVERAGE_VERSION}
|
|
WHERE "id" = 1
|
|
AND "coverage_version" = ${expectedVersion}
|
|
`);
|
|
if (updated !== 1) {
|
|
throw new Error('Read-model coverage activation lost its version compare-and-set.');
|
|
}
|
|
}
|
|
|
|
return {
|
|
previousVersion: current,
|
|
coverageVersion: READ_MODEL_REVISION_COVERAGE_VERSION,
|
|
seededHeads,
|
|
};
|
|
};
|