Batch rank persistence for large scenario startup

This commit is contained in:
2026-07-26 05:37:40 +00:00
parent 27945fbca9
commit 7679e8f5b7
2 changed files with 32 additions and 19 deletions
+25 -18
View File
@@ -1,6 +1,6 @@
import {
createGamePostgresConnector,
type GamePrisma,
GamePrisma,
type InputJsonValue,
type TurnEngineCityUpdateInput,
type TurnEngineDiplomacyCreateManyInput,
@@ -379,6 +379,29 @@ const buildInitialRankRows = (
): Array<{ generalId: number; nationId: number; type: string; value: number }> =>
buildRankRows(general).map((row) => ({ ...row, nationId: 0, value: 0 }));
const RANK_DATA_UPSERT_BATCH_SIZE = 1_000;
const upsertRankRows = async (
prisma: GamePrisma.TransactionClient,
rows: Array<{ generalId: number; nationId: number; type: string; value: number }>
): Promise<void> => {
for (let offset = 0; offset < rows.length; offset += RANK_DATA_UPSERT_BATCH_SIZE) {
const batch = rows.slice(offset, offset + RANK_DATA_UPSERT_BATCH_SIZE);
await prisma.$executeRaw(GamePrisma.sql`
INSERT INTO "rank_data" ("general_id", "nation_id", "type", "value")
VALUES ${GamePrisma.join(
batch.map(
(row) => GamePrisma.sql`(${row.generalId}, ${row.nationId}, ${row.type}, ${row.value})`
)
)}
ON CONFLICT ("general_id", "type") DO UPDATE
SET
"nation_id" = EXCLUDED."nation_id",
"value" = EXCLUDED."value"
`);
}
};
const buildGeneralUpdate = (
general: ReturnType<InMemoryTurnWorld['consumeDirtyState']>['generals'][number]
): TurnEngineGeneralUpdateInput => ({
@@ -955,23 +978,7 @@ export const createDatabaseTurnHooks = async (
...createdGenerals.flatMap(buildInitialRankRows),
...rankTargets.flatMap(buildRankRows),
];
await Promise.all(
rankRows.map((row) =>
prisma.rankData.upsert({
where: {
generalId_type: {
generalId: row.generalId,
type: row.type,
},
},
update: {
nationId: row.nationId,
value: row.value,
},
create: row,
})
)
);
await upsertRankRows(prisma, rankRows);
}
if (logs.length > 0) {
@@ -46,9 +46,15 @@ const resetScenario = async (page: Page, scenarioId: string, sourceCommit: strin
await expect(latestOperation.locator('td').nth(3)).toHaveText('SUCCEEDED', {
timeout: 300_000,
});
await expect(page.getByTestId('selected-profile-status').locator('.text-emerald-400')).toHaveCount(2, {
const profileStatus = page.getByTestId('selected-profile-status');
await expect(profileStatus.locator(':scope > div').nth(0)).toContainText('RUNNING', {
timeout: 30_000,
});
await expect(profileStatus.locator(':scope > div').nth(1)).toContainText('SUCCEEDED');
await expect(profileStatus.locator('.text-emerald-400')).toHaveCount(3, {
timeout: 30_000,
});
await expect(profileStatus.locator('..').locator('.text-red-400')).toHaveCount(0);
};
const createGeneralAndInspectIcons = async (