@axinom/mosaic-db-common 0.25.1-rc.8 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/replication/create-logical-replication-service.d.ts +12 -1
- package/dist/replication/create-logical-replication-service.d.ts.map +1 -1
- package/dist/replication/create-logical-replication-service.js +38 -12
- package/dist/replication/create-logical-replication-service.js.map +1 -1
- package/package.json +2 -2
- package/src/replication/create-logical-replication-service.ts +92 -21
|
@@ -43,6 +43,17 @@ export interface LogicalReplicationServiceConfig {
|
|
|
43
43
|
* passed, console logs are used instead.
|
|
44
44
|
*/
|
|
45
45
|
logger?: DbLogger;
|
|
46
|
+
/**
|
|
47
|
+
* When logical replication service establishes a connection with a
|
|
48
|
+
* replication slot - it can already be in use, e.g. by another instance of
|
|
49
|
+
* the service. The logical replication service will then try to reconnect
|
|
50
|
+
* once every 10 seconds for a total duration of 5 minutes.
|
|
51
|
+
*
|
|
52
|
+
* After the first 5 minutes, the delay of reconnection attempts will be
|
|
53
|
+
* increased from 10 seconds to the value of this parameter. Default value is
|
|
54
|
+
* 300000 (every 5 minutes).
|
|
55
|
+
*/
|
|
56
|
+
reconnectionIntervalInMs?: number;
|
|
46
57
|
}
|
|
47
58
|
/**
|
|
48
59
|
* Creates a logical replication service and keeps it running until stopped.
|
|
@@ -62,7 +73,7 @@ export interface LogicalReplicationServiceConfig {
|
|
|
62
73
|
* service.
|
|
63
74
|
* @returns a function to stop the logical replication service.
|
|
64
75
|
*/
|
|
65
|
-
export declare const createLogicalReplicationService: ({ connectionString, publicationNames, replicationSlotName, messageHandler, operationsToWatch, logger: passedLogger, }: LogicalReplicationServiceConfig) => Promise<{
|
|
76
|
+
export declare const createLogicalReplicationService: ({ connectionString, publicationNames, replicationSlotName, messageHandler, operationsToWatch, logger: passedLogger, reconnectionIntervalInMs, }: LogicalReplicationServiceConfig) => Promise<{
|
|
66
77
|
(): Promise<void>;
|
|
67
78
|
}>;
|
|
68
79
|
//# sourceMappingURL=create-logical-replication-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-logical-replication-service.d.ts","sourceRoot":"","sources":["../../src/replication/create-logical-replication-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create-logical-replication-service.d.ts","sourceRoot":"","sources":["../../src/replication/create-logical-replication-service.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,QAAQ,EAET,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAKrC,MAAM,MAAM,2BAA2B,GACnC,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,UAAU,GACV,UAAU,GACV,MAAM,GACN,QAAQ,CAAC;AAEb,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,2BAA2B,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE1B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,qCAAqC;IACpD,aAAa,EAAE,qBAAqB,CAAC;IACrC,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC;CAC/B;AAED,MAAM,MAAM,gCAAgC,GAAG,CAC7C,MAAM,EAAE,qCAAqC,KAC1C,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAI9C,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B;;;OAGG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,cAAc,EAAE,gCAAgC,CAAC;IAEjD;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,2BAA2B,EAAE,CAAC;IAClD;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB;;;;;;;;;OASG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAoED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,+BAA+B,oJAQzC,+BAA+B,KAAG,QAAQ;IAAE,IAAI,QAAQ,IAAI,CAAC,CAAA;CAAE,CAoHjE,CAAC"}
|
|
@@ -15,6 +15,33 @@ const getScopedMessage = (message, operationsToWatch) => {
|
|
|
15
15
|
old: 'old' in message && message.old ? message.old : undefined,
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
|
+
const handleReconnection = async (error, reconnectionAttempts, replicationSlotName, reconnectionIntervalInMs, logger) => {
|
|
19
|
+
if ((error === null || error === void 0 ? void 0 : error.code) === '55006' && (error === null || error === void 0 ? void 0 : error.routine) === 'ReplicationSlotAcquire') {
|
|
20
|
+
const isFirstFiveMinutesAfterStartup = reconnectionAttempts < 30;
|
|
21
|
+
const log = isFirstFiveMinutesAfterStartup
|
|
22
|
+
? logger.trace.bind(logger)
|
|
23
|
+
: logger.log.bind(logger);
|
|
24
|
+
const waitingTimeInMs = isFirstFiveMinutesAfterStartup
|
|
25
|
+
? 10000
|
|
26
|
+
: reconnectionIntervalInMs;
|
|
27
|
+
log(`The logical replication slot '${replicationSlotName}' is already in use. Waiting for ${waitingTimeInMs / 1000} seconds to try connecting again. (Attempt ${reconnectionAttempts + 1})`);
|
|
28
|
+
await sleep(waitingTimeInMs);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
33
|
+
const handleFailure = async (error, failedAttempts, failedAttemptsResetTimer, logger) => {
|
|
34
|
+
if (failedAttempts > 20) {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
logger.error(error, `Logical replication service failure has occurred. (Attempt ${failedAttempts})`);
|
|
38
|
+
await sleep(1500 * failedAttempts);
|
|
39
|
+
clearTimeout(failedAttemptsResetTimer);
|
|
40
|
+
return setTimeout(async () => {
|
|
41
|
+
logger.trace(`No errors have occurred within 5 minutes. Resetting failures counter after ${failedAttempts} failure(s).`);
|
|
42
|
+
failedAttempts = 0;
|
|
43
|
+
}, 300000);
|
|
44
|
+
};
|
|
18
45
|
/**
|
|
19
46
|
* Creates a logical replication service and keeps it running until stopped.
|
|
20
47
|
* Watches the table changes based on passed publication names and replication
|
|
@@ -33,7 +60,7 @@ const getScopedMessage = (message, operationsToWatch) => {
|
|
|
33
60
|
* service.
|
|
34
61
|
* @returns a function to stop the logical replication service.
|
|
35
62
|
*/
|
|
36
|
-
const createLogicalReplicationService = async ({ connectionString, publicationNames, replicationSlotName, messageHandler, operationsToWatch = ['insert', 'update', 'delete'], logger: passedLogger, }) => {
|
|
63
|
+
const createLogicalReplicationService = async ({ connectionString, publicationNames, replicationSlotName, messageHandler, operationsToWatch = ['insert', 'update', 'delete'], logger: passedLogger, reconnectionIntervalInMs = 300000, }) => {
|
|
37
64
|
if (operationsToWatch.length === 0) {
|
|
38
65
|
throw new Error('Unable to start the logical replication service when operationsToWatch is an empty array.');
|
|
39
66
|
}
|
|
@@ -42,6 +69,7 @@ const createLogicalReplicationService = async ({ connectionString, publicationNa
|
|
|
42
69
|
let service;
|
|
43
70
|
let stopped = false;
|
|
44
71
|
let failedAttempts = 0;
|
|
72
|
+
let reconnectionAttempts = 0;
|
|
45
73
|
let failedAttemptsResetTimer = undefined;
|
|
46
74
|
// Run the service in an endless background loop until it gets stopped
|
|
47
75
|
(async () => {
|
|
@@ -50,6 +78,9 @@ const createLogicalReplicationService = async ({ connectionString, publicationNa
|
|
|
50
78
|
await new Promise((resolve, reject) => {
|
|
51
79
|
let heartbeatAckTimer = undefined;
|
|
52
80
|
service = new pg_logical_replication_1.LogicalReplicationService({ connectionString }, { acknowledge: { auto: false, timeoutSeconds: 0 } });
|
|
81
|
+
service.on('start', async () => {
|
|
82
|
+
logger.debug(`Started the logical replication service to watch the database operations (${operationsToWatch.join(', ')}) on table(s) associated with the following publication(s): ${publicationNames.join(', ')}`);
|
|
83
|
+
});
|
|
53
84
|
service.on('data', async (lsn, message) => {
|
|
54
85
|
try {
|
|
55
86
|
if (service.isStop()) {
|
|
@@ -95,21 +126,16 @@ const createLogicalReplicationService = async ({ connectionString, publicationNa
|
|
|
95
126
|
});
|
|
96
127
|
}
|
|
97
128
|
catch (err) {
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
129
|
+
const error = err;
|
|
130
|
+
if (await handleReconnection(error, reconnectionAttempts, replicationSlotName, reconnectionIntervalInMs, logger)) {
|
|
131
|
+
reconnectionAttempts++;
|
|
132
|
+
continue;
|
|
101
133
|
}
|
|
102
|
-
|
|
103
|
-
await
|
|
104
|
-
clearTimeout(failedAttemptsResetTimer);
|
|
105
|
-
failedAttemptsResetTimer = setTimeout(async () => {
|
|
106
|
-
logger.trace(`No errors have occurred within 5 minutes. Resetting failures counter after ${failedAttempts} failure(s).`);
|
|
107
|
-
failedAttempts = 0;
|
|
108
|
-
}, 300000);
|
|
134
|
+
failedAttempts++;
|
|
135
|
+
failedAttemptsResetTimer = await handleFailure(error, failedAttempts, failedAttemptsResetTimer, logger);
|
|
109
136
|
}
|
|
110
137
|
}
|
|
111
138
|
})();
|
|
112
|
-
logger.debug(`Started the logical replication service to watch the database operations (${operationsToWatch.join(', ')}) on table(s) associated with the following publication(s): ${publicationNames.join(', ')}`);
|
|
113
139
|
return async () => {
|
|
114
140
|
logger.debug('Shutting down the logical replication service.');
|
|
115
141
|
stopped = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-logical-replication-service.js","sourceRoot":"","sources":["../../src/replication/create-logical-replication-service.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"create-logical-replication-service.js","sourceRoot":"","sources":["../../src/replication/create-logical-replication-service.ts"],"names":[],"mappings":";;;AACA,mEAIgC;AAGhC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAC1C,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;AAiF5C,MAAM,gBAAgB,GAAG,CACvB,OAAyB,EACzB,iBAA2B,EACQ,EAAE;IACrC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QAC5C,OAAO,SAAS,CAAC;KAClB;IAED,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,GAAG;QACtB,SAAS,EAAE,UAAU,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACpE,UAAU,EAAE,UAAU,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QACvE,GAAG,EAAE,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;QAC/C,GAAG,EAAE,KAAK,IAAI,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;KAC/D,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,KAAK,EAC9B,KAAoB,EACpB,oBAA4B,EAC5B,mBAA2B,EAC3B,wBAAgC,EAChC,MAAgB,EACE,EAAE;IACpB,IAAI,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,MAAK,OAAO,IAAI,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,OAAO,MAAK,wBAAwB,EAAE;QAC1E,MAAM,8BAA8B,GAAG,oBAAoB,GAAG,EAAE,CAAC;QACjE,MAAM,GAAG,GAAG,8BAA8B;YACxC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;YAC3B,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5B,MAAM,eAAe,GAAG,8BAA8B;YACpD,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,wBAAwB,CAAC;QAC7B,GAAG,CACD,iCAAiC,mBAAmB,oCAClD,eAAe,GAAG,IACpB,8CAA8C,oBAAoB,GAAG,CAAC,GAAG,CAC1E,CAAC;QACF,MAAM,KAAK,CAAC,eAAe,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC;KACb;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,KAAK,EACzB,KAAY,EACZ,cAAsB,EACtB,wBAAoD,EACpD,MAAgB,EACqB,EAAE;IACvC,IAAI,cAAc,GAAG,EAAE,EAAE;QACvB,MAAM,KAAK,CAAC;KACb;IACD,MAAM,CAAC,KAAK,CACV,KAAK,EACL,8DAA8D,cAAc,GAAG,CAChF,CAAC;IACF,MAAM,KAAK,CAAC,IAAI,GAAG,cAAc,CAAC,CAAC;IACnC,YAAY,CAAC,wBAAwB,CAAC,CAAC;IACvC,OAAO,UAAU,CAAC,KAAK,IAAI,EAAE;QAC3B,MAAM,CAAC,KAAK,CACV,8EAA8E,cAAc,cAAc,CAC3G,CAAC;QACF,cAAc,GAAG,CAAC,CAAC;IACrB,CAAC,EAAE,MAAM,CAAC,CAAC;AACb,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACI,MAAM,+BAA+B,GAAG,KAAK,EAAE,EACpD,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,iBAAiB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAClD,MAAM,EAAE,YAAY,EACpB,wBAAwB,GAAG,MAAM,GACD,EAAkC,EAAE;IACpE,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE;QAClC,MAAM,IAAI,KAAK,CACb,2FAA2F,CAC5F,CAAC;KACH;IAED,MAAM,MAAM,GAAG,YAAY,aAAZ,YAAY,cAAZ,YAAY,GAAI,OAAO,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,uCAAc,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACzE,IAAI,OAAkC,CAAC;IACvC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,oBAAoB,GAAG,CAAC,CAAC;IAC7B,IAAI,wBAAwB,GAA+B,SAAS,CAAC;IACrE,sEAAsE;IACtE,CAAC,KAAK,IAAI,EAAE;QACV,OAAO,CAAC,OAAO,EAAE;YACf,IAAI;gBACF,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;oBACpC,IAAI,iBAAiB,GAA+B,SAAS,CAAC;oBAC9D,OAAO,GAAG,IAAI,kDAAyB,CACrC,EAAE,gBAAgB,EAAE,EACpB,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE,CACpD,CAAC;oBACF,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;wBAC7B,MAAM,CAAC,KAAK,CACV,6EAA6E,iBAAiB,CAAC,IAAI,CACjG,IAAI,CACL,+DAA+D,gBAAgB,CAAC,IAAI,CACnF,IAAI,CACL,EAAE,CACJ,CAAC;oBACJ,CAAC,CAAC,CAAC;oBACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,GAAW,EAAE,OAAyB,EAAE,EAAE;wBAClE,IAAI;4BACF,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE;gCACpB,MAAM,CAAC,KAAK,CACV,mDAAmD,CACpD,CAAC;gCACF,OAAO;6BACR;4BACD,MAAM,aAAa,GAAG,gBAAgB,CACpC,OAAO,EACP,iBAAiB,CAClB,CAAC;4BACF,IAAI,aAAa,EAAE;gCACjB,MAAM,cAAc,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;gCAC9D,cAAc,GAAG,CAAC,CAAC;gCACnB,YAAY,CAAC,wBAAwB,CAAC,CAAC;gCACvC,YAAY,CAAC,iBAAiB,CAAC,CAAC;gCAChC,MAAM,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;6BAChC;yBACF;wBAAC,OAAO,KAAK,EAAE;4BACd,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE;gCACrB,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;6BAC9B;yBACF;oBACH,CAAC,CAAC,CAAC;oBACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,GAAU,EAAE,EAAE;wBACvC,OAAO,CAAC,kBAAkB,EAAE,CAAC;wBAC7B,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;wBACrB,MAAM,CAAC,GAAG,CAAC,CAAC;oBACd,CAAC,CAAC,CAAC;oBACH,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE;wBAC/D,IAAI,aAAa,EAAE;4BACjB,iBAAiB,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;gCACxC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,0BAA0B,CAAC,CAAC;gCAC/C,MAAM,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;4BACjC,CAAC,EAAE,IAAI,CAAC,CAAC;yBACV;oBACH,CAAC,CAAC,CAAC;oBACH,OAAO;yBACJ,SAAS,CAAC,MAAM,EAAE,mBAAmB,CAAC;yBACtC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;yBACzB,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;wBACnB,OAAO,CAAC,kBAAkB,EAAE,CAAC;wBAC7B,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;wBACrB,MAAM,CAAC,GAAG,CAAC,CAAC;oBACd,CAAC,CAAC,CAAC;gBACP,CAAC,CAAC,CAAC;aACJ;YAAC,OAAO,GAAG,EAAE;gBACZ,MAAM,KAAK,GAAG,GAAoB,CAAC;gBAEnC,IACE,MAAM,kBAAkB,CACtB,KAAK,EACL,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,MAAM,CACP,EACD;oBACA,oBAAoB,EAAE,CAAC;oBACvB,SAAS;iBACV;gBAED,cAAc,EAAE,CAAC;gBACjB,wBAAwB,GAAG,MAAM,aAAa,CAC5C,KAAK,EACL,cAAc,EACd,wBAAwB,EACxB,MAAM,CACP,CAAC;aACH;SACF;IACH,CAAC,CAAC,EAAE,CAAC;IACL,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAC/D,OAAO,GAAG,IAAI,CAAC;QACf,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,kBAAkB,EAAE,CAAC;QAC9B,OAAO,aAAP,OAAO,uBAAP,OAAO,CACH,IAAI,GACL,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CACX,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,+CAA+C,CAAC,CACjE,CAAC;IACN,CAAC,CAAC;AACJ,CAAC,CAAC;AA5HW,QAAA,+BAA+B,mCA4H1C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-db-common",
|
|
3
|
-
"version": "0.25.1
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "This library encapsulates database-related functionality to develop Mosaic based services.",
|
|
5
5
|
"author": "Axinom",
|
|
6
6
|
"license": "PROPRIETARY",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"publishConfig": {
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "4e4a3aa0d5a0efa7dc2da0e52fa5610d2a771498"
|
|
56
56
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DatabaseError } from 'pg';
|
|
1
2
|
import {
|
|
2
3
|
LogicalReplicationService,
|
|
3
4
|
Pgoutput,
|
|
@@ -74,6 +75,17 @@ export interface LogicalReplicationServiceConfig {
|
|
|
74
75
|
* passed, console logs are used instead.
|
|
75
76
|
*/
|
|
76
77
|
logger?: DbLogger;
|
|
78
|
+
/**
|
|
79
|
+
* When logical replication service establishes a connection with a
|
|
80
|
+
* replication slot - it can already be in use, e.g. by another instance of
|
|
81
|
+
* the service. The logical replication service will then try to reconnect
|
|
82
|
+
* once every 10 seconds for a total duration of 5 minutes.
|
|
83
|
+
*
|
|
84
|
+
* After the first 5 minutes, the delay of reconnection attempts will be
|
|
85
|
+
* increased from 10 seconds to the value of this parameter. Default value is
|
|
86
|
+
* 300000 (every 5 minutes).
|
|
87
|
+
*/
|
|
88
|
+
reconnectionIntervalInMs?: number;
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
const getScopedMessage = (
|
|
@@ -93,6 +105,55 @@ const getScopedMessage = (
|
|
|
93
105
|
};
|
|
94
106
|
};
|
|
95
107
|
|
|
108
|
+
const handleReconnection = async (
|
|
109
|
+
error: DatabaseError,
|
|
110
|
+
reconnectionAttempts: number,
|
|
111
|
+
replicationSlotName: string,
|
|
112
|
+
reconnectionIntervalInMs: number,
|
|
113
|
+
logger: DbLogger,
|
|
114
|
+
): Promise<boolean> => {
|
|
115
|
+
if (error?.code === '55006' && error?.routine === 'ReplicationSlotAcquire') {
|
|
116
|
+
const isFirstFiveMinutesAfterStartup = reconnectionAttempts < 30;
|
|
117
|
+
const log = isFirstFiveMinutesAfterStartup
|
|
118
|
+
? logger.trace.bind(logger)
|
|
119
|
+
: logger.log.bind(logger);
|
|
120
|
+
const waitingTimeInMs = isFirstFiveMinutesAfterStartup
|
|
121
|
+
? 10000
|
|
122
|
+
: reconnectionIntervalInMs;
|
|
123
|
+
log(
|
|
124
|
+
`The logical replication slot '${replicationSlotName}' is already in use. Waiting for ${
|
|
125
|
+
waitingTimeInMs / 1000
|
|
126
|
+
} seconds to try connecting again. (Attempt ${reconnectionAttempts + 1})`,
|
|
127
|
+
);
|
|
128
|
+
await sleep(waitingTimeInMs);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const handleFailure = async (
|
|
135
|
+
error: Error,
|
|
136
|
+
failedAttempts: number,
|
|
137
|
+
failedAttemptsResetTimer: NodeJS.Timeout | undefined,
|
|
138
|
+
logger: DbLogger,
|
|
139
|
+
): Promise<NodeJS.Timeout | undefined> => {
|
|
140
|
+
if (failedAttempts > 20) {
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
logger.error(
|
|
144
|
+
error,
|
|
145
|
+
`Logical replication service failure has occurred. (Attempt ${failedAttempts})`,
|
|
146
|
+
);
|
|
147
|
+
await sleep(1500 * failedAttempts);
|
|
148
|
+
clearTimeout(failedAttemptsResetTimer);
|
|
149
|
+
return setTimeout(async () => {
|
|
150
|
+
logger.trace(
|
|
151
|
+
`No errors have occurred within 5 minutes. Resetting failures counter after ${failedAttempts} failure(s).`,
|
|
152
|
+
);
|
|
153
|
+
failedAttempts = 0;
|
|
154
|
+
}, 300000);
|
|
155
|
+
};
|
|
156
|
+
|
|
96
157
|
/**
|
|
97
158
|
* Creates a logical replication service and keeps it running until stopped.
|
|
98
159
|
* Watches the table changes based on passed publication names and replication
|
|
@@ -118,6 +179,7 @@ export const createLogicalReplicationService = async ({
|
|
|
118
179
|
messageHandler,
|
|
119
180
|
operationsToWatch = ['insert', 'update', 'delete'],
|
|
120
181
|
logger: passedLogger,
|
|
182
|
+
reconnectionIntervalInMs = 300000,
|
|
121
183
|
}: LogicalReplicationServiceConfig): Promise<{ (): Promise<void> }> => {
|
|
122
184
|
if (operationsToWatch.length === 0) {
|
|
123
185
|
throw new Error(
|
|
@@ -130,6 +192,7 @@ export const createLogicalReplicationService = async ({
|
|
|
130
192
|
let service: LogicalReplicationService;
|
|
131
193
|
let stopped = false;
|
|
132
194
|
let failedAttempts = 0;
|
|
195
|
+
let reconnectionAttempts = 0;
|
|
133
196
|
let failedAttemptsResetTimer: NodeJS.Timeout | undefined = undefined;
|
|
134
197
|
// Run the service in an endless background loop until it gets stopped
|
|
135
198
|
(async () => {
|
|
@@ -141,6 +204,15 @@ export const createLogicalReplicationService = async ({
|
|
|
141
204
|
{ connectionString },
|
|
142
205
|
{ acknowledge: { auto: false, timeoutSeconds: 0 } },
|
|
143
206
|
);
|
|
207
|
+
service.on('start', async () => {
|
|
208
|
+
logger.debug(
|
|
209
|
+
`Started the logical replication service to watch the database operations (${operationsToWatch.join(
|
|
210
|
+
', ',
|
|
211
|
+
)}) on table(s) associated with the following publication(s): ${publicationNames.join(
|
|
212
|
+
', ',
|
|
213
|
+
)}`,
|
|
214
|
+
);
|
|
215
|
+
});
|
|
144
216
|
service.on('data', async (lsn: string, message: Pgoutput.Message) => {
|
|
145
217
|
try {
|
|
146
218
|
if (service.isStop()) {
|
|
@@ -189,32 +261,31 @@ export const createLogicalReplicationService = async ({
|
|
|
189
261
|
});
|
|
190
262
|
});
|
|
191
263
|
} catch (err) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
264
|
+
const error = err as DatabaseError;
|
|
265
|
+
|
|
266
|
+
if (
|
|
267
|
+
await handleReconnection(
|
|
268
|
+
error,
|
|
269
|
+
reconnectionAttempts,
|
|
270
|
+
replicationSlotName,
|
|
271
|
+
reconnectionIntervalInMs,
|
|
272
|
+
logger,
|
|
273
|
+
)
|
|
274
|
+
) {
|
|
275
|
+
reconnectionAttempts++;
|
|
276
|
+
continue;
|
|
195
277
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
278
|
+
|
|
279
|
+
failedAttempts++;
|
|
280
|
+
failedAttemptsResetTimer = await handleFailure(
|
|
281
|
+
error,
|
|
282
|
+
failedAttempts,
|
|
283
|
+
failedAttemptsResetTimer,
|
|
284
|
+
logger,
|
|
199
285
|
);
|
|
200
|
-
await sleep(1500 * failedAttempts);
|
|
201
|
-
clearTimeout(failedAttemptsResetTimer);
|
|
202
|
-
failedAttemptsResetTimer = setTimeout(async () => {
|
|
203
|
-
logger.trace(
|
|
204
|
-
`No errors have occurred within 5 minutes. Resetting failures counter after ${failedAttempts} failure(s).`,
|
|
205
|
-
);
|
|
206
|
-
failedAttempts = 0;
|
|
207
|
-
}, 300000);
|
|
208
286
|
}
|
|
209
287
|
}
|
|
210
288
|
})();
|
|
211
|
-
logger.debug(
|
|
212
|
-
`Started the logical replication service to watch the database operations (${operationsToWatch.join(
|
|
213
|
-
', ',
|
|
214
|
-
)}) on table(s) associated with the following publication(s): ${publicationNames.join(
|
|
215
|
-
', ',
|
|
216
|
-
)}`,
|
|
217
|
-
);
|
|
218
289
|
return async () => {
|
|
219
290
|
logger.debug('Shutting down the logical replication service.');
|
|
220
291
|
stopped = true;
|