From a79c06d4f371c325b4f4c847c147ed8989ba0660 Mon Sep 17 00:00:00 2001 From: XuJiacheng Date: Fri, 3 Apr 2026 18:46:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(db):=20=E5=90=AF=E7=94=A8=E5=86=B2=E7=AA=81?= =?UTF-8?q?=E9=94=AE=E6=8E=92=E5=BA=8F=E5=8E=BB=E9=87=8D=E5=B9=B6=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=AD=BB=E9=94=81=E9=87=8D=E8=AF=95=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改数据库管理器配置,默认启用冲突键排序去重功能,保留最大时间戳记录 增加死锁重试机制,默认重试3次,基础延迟100毫秒 添加相关测试用例验证排序去重和死锁重试功能 --- src/db/databaseManager.js | 4 +-- test/dualWrite.test.js | 62 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/db/databaseManager.js b/src/db/databaseManager.js index 19d813f..3368b41 100644 --- a/src/db/databaseManager.js +++ b/src/db/databaseManager.js @@ -931,8 +931,8 @@ class DatabaseManager { tableRef: this.config.roomStatusTable ?? 'room_status.room_status_moment', forceOnlineStatusOnWrite: false, forceUpdateOnConflict: false, - sortAndDedupByConflictKey: false, - deadlockRetryAttempts: 0, + sortAndDedupByConflictKey: true, + deadlockRetryAttempts: 3, deadlockRetryBaseDelayMs: 100, logPrefix: 'upsertRoomStatus', }); diff --git a/test/dualWrite.test.js b/test/dualWrite.test.js index bf90a43..0be5032 100644 --- a/test/dualWrite.test.js +++ b/test/dualWrite.test.js @@ -692,6 +692,68 @@ describe('DatabaseManager: room_status upsert SQL', () => { assert.equal(result.rowCount, 1); assert.equal(calls, 3); }); + + it('sorts and dedups old 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' }); + const built = dm._buildRoomStatusUpsertQuery([ + { ...buildBasePayload(), hotel_id: 2, room_id: '102', device_id: 'dev-b', ts_ms: 1000 }, + { ...buildBasePayload(), hotel_id: 1, room_id: '101', device_id: 'dev-a', ts_ms: 3000 }, + { ...buildBasePayload(), hotel_id: 2, room_id: '102', device_id: 'dev-b', ts_ms: 5000 }, + ], { + tableName: 'room_status.room_status_moment', + conflictColumns: ['hotel_id', 'room_id', 'device_id'], + includeGuid: true, + autoCreatePartitions: true, + tableRef: 'room_status.room_status_moment', + forceOnlineStatusOnWrite: false, + forceUpdateOnConflict: false, + sortAndDedupByConflictKey: true, + logPrefix: 'upsertRoomStatus', + }); + + 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[0].device_id, 'dev-a'); + assert.equal(built.uniqueEvents[1].hotel_id, 2); + assert.equal(built.uniqueEvents[1].room_id, '102'); + assert.equal(built.uniqueEvents[1].device_id, 'dev-b'); + assert.equal(built.uniqueEvents[1].ts_ms, 5000); + }); + + it('retries old 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' }); + 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', + conflictColumns: ['hotel_id', 'room_id', 'device_id'], + includeGuid: true, + autoCreatePartitions: true, + tableRef: 'room_status.room_status_moment', + forceOnlineStatusOnWrite: false, + forceUpdateOnConflict: false, + sortAndDedupByConflictKey: true, + deadlockRetryAttempts: 3, + deadlockRetryBaseDelayMs: 1, + logPrefix: 'upsertRoomStatus', + }); + + assert.equal(result.rowCount, 1); + assert.equal(calls, 3); + }); }); describe('DatabaseManager: insertHeartbeatEventsDual', () => {