feat: 添加 G5 room_status 写入的冲突键排序与去重功能,并实现死锁自动重试机制

This commit is contained in:
2026-03-24 09:54:46 +08:00
parent f14d8b155c
commit 1ee0988ac6
3 changed files with 139 additions and 21 deletions

View File

@@ -625,11 +625,73 @@ describe('DatabaseManager: room_status upsert SQL', () => {
tableRef: 'room_status.room_status_moment_g5',
forceOnlineStatusOnWrite: true,
forceUpdateOnConflict: true,
sortAndDedupByConflictKey: true,
logPrefix: 'upsertRoomStatusG5',
});
assert.doesNotMatch(built.sql, /room_status\.room_status_moment_g5\.ts_ms <= EXCLUDED\.ts_ms/);
assert.doesNotMatch(built.sql, /IS DISTINCT FROM EXCLUDED/);
});
it('sorts and dedups g5 room_status by conflict key, keeping max ts_ms', () => {
const dm = new DatabaseManager({ host: 'x', port: 5432, user: 'x', password: 'x', database: 'x', roomStatusTable: 'room_status.room_status_moment_g5' });
const built = dm._buildRoomStatusUpsertQuery([
{ ...buildBasePayload(), hotel_id: 2, room_id: '102', ts_ms: 1000, device_id: 'dev-old-102' },
{ ...buildBasePayload(), hotel_id: 1, room_id: '101', ts_ms: 3000, device_id: 'dev-101' },
{ ...buildBasePayload(), hotel_id: 2, room_id: '102', ts_ms: 5000, device_id: 'dev-new-102' },
], {
tableName: 'room_status.room_status_moment_g5',
conflictColumns: ['hotel_id', 'room_id'],
includeGuid: false,
autoCreatePartitions: false,
tableRef: 'room_status.room_status_moment_g5',
forceOnlineStatusOnWrite: true,
forceUpdateOnConflict: true,
sortAndDedupByConflictKey: true,
logPrefix: 'upsertRoomStatusG5',
});
assert.equal(built.uniqueEvents.length, 2);
assert.equal(built.uniqueEvents[0].hotel_id, 1);
assert.equal(built.uniqueEvents[0].room_id, '101');
assert.equal(built.uniqueEvents[1].hotel_id, 2);
assert.equal(built.uniqueEvents[1].room_id, '102');
assert.equal(built.uniqueEvents[1].ts_ms, 5000);
assert.equal(built.uniqueEvents[1].device_id, 'dev-new-102');
});
it('retries g5 room_status on deadlock', async () => {
const dm = new DatabaseManager({ host: 'x', port: 5432, user: 'x', password: 'x', database: 'x', roomStatusTable: 'room_status.room_status_moment_g5' });
let calls = 0;
dm.pool = {
query: async () => {
calls += 1;
if (calls < 3) {
const err = new Error('deadlock detected');
err.code = '40P01';
throw err;
}
return { rowCount: 1 };
},
};
dm._sleep = async () => {};
const result = await dm._upsertRoomStatusToTarget([buildBasePayload()], {
tableName: 'room_status.room_status_moment_g5',
conflictColumns: ['hotel_id', 'room_id'],
includeGuid: false,
autoCreatePartitions: false,
tableRef: 'room_status.room_status_moment_g5',
forceOnlineStatusOnWrite: true,
forceUpdateOnConflict: true,
sortAndDedupByConflictKey: true,
deadlockRetryAttempts: 3,
deadlockRetryBaseDelayMs: 1,
logPrefix: 'upsertRoomStatusG5',
});
assert.equal(result.rowCount, 1);
assert.equal(calls, 3);
});
});
describe('DatabaseManager: insertHeartbeatEventsDual', () => {