@live-change/backup-service 0.9.209 → 0.9.213
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/clear.js +31 -0
- package/index.js +130 -30
- package/package.json +5 -5
package/clear.js
CHANGED
|
@@ -97,4 +97,35 @@ export async function removeOldCommands(before, options) {
|
|
|
97
97
|
if(options.delay) await new Promise(resolve => setTimeout(resolve, options.delay))
|
|
98
98
|
more = deleteStats.count >= bucket
|
|
99
99
|
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const clearEventReportsQuery = `${async (input, output, { indexName, idPrefix, bucket, before }) => {
|
|
103
|
+
const indexReader = await input.index(indexName)
|
|
104
|
+
const tableWriter = await output.table('eventReports')
|
|
105
|
+
const pointers = await indexReader.rangeGet({ limit: bucket, lt: before })
|
|
106
|
+
for(const pointer of pointers) {
|
|
107
|
+
await tableWriter.delete(idPrefix + pointer.to)
|
|
108
|
+
}
|
|
109
|
+
await output.put({ count: pointers.length })
|
|
110
|
+
}}`
|
|
111
|
+
|
|
112
|
+
async function removeOldEventReportsForIndex(indexName, idPrefix, before, options) {
|
|
113
|
+
const bucket = options?.bucket || 128
|
|
114
|
+
let more = true
|
|
115
|
+
while(more) {
|
|
116
|
+
const queryResult = await app.dao.request(['database', 'query'], app.databaseName, clearEventReportsQuery,
|
|
117
|
+
{ indexName, idPrefix, bucket, before })
|
|
118
|
+
const deleteStats = queryResult[0]
|
|
119
|
+
if(options?.delay) await new Promise(resolve => setTimeout(resolve, options.delay))
|
|
120
|
+
more = deleteStats.count >= bucket
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function removeOldEventReports(commandBefore, triggerBefore, options = {}) {
|
|
125
|
+
if(!app.splitCommands && commandBefore) {
|
|
126
|
+
await removeOldEventReportsForIndex('commands_byTimestamp', 'command_', commandBefore, options)
|
|
127
|
+
}
|
|
128
|
+
if(!app.splitTriggers && triggerBefore) {
|
|
129
|
+
await removeOldEventReportsForIndex('triggers_byTimestamp', 'trigger_', triggerBefore, options)
|
|
130
|
+
}
|
|
100
131
|
}
|
package/index.js
CHANGED
|
@@ -12,13 +12,15 @@ import { createBackup, createBackupLogger, currentBackupPath, removeOldBackups }
|
|
|
12
12
|
import { restoreBackup } from './restore.js'
|
|
13
13
|
import {
|
|
14
14
|
getLastEventId, removeOldEvents, removeOldOpLogs,
|
|
15
|
-
getLastCommandTimestamp, getLastTriggerTimestamp, removeOldTriggers, removeOldCommands
|
|
15
|
+
getLastCommandTimestamp, getLastTriggerTimestamp, removeOldTriggers, removeOldCommands,
|
|
16
|
+
removeOldEventReports
|
|
16
17
|
} from './clear.js'
|
|
17
18
|
|
|
18
19
|
import progress from "progress-stream"
|
|
19
20
|
|
|
20
21
|
const TWENTY_FOUR_HOURS = 24 * 3600 * 1000
|
|
21
22
|
const TEN_MINUTES = 10 * 60 * 1000
|
|
23
|
+
const DEFAULT_OP_LOG_RETENTION_MS = 60 * 60 * 1000
|
|
22
24
|
|
|
23
25
|
let currentBackup = null
|
|
24
26
|
|
|
@@ -43,7 +45,7 @@ if(backupServerUsername && backupServerPassword ) {
|
|
|
43
45
|
users[backupServerUsername] = backupServerPassword
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
if(Object.keys(users) > 0) {
|
|
48
|
+
if(Object.keys(users).length > 0) {
|
|
47
49
|
expressApp.use(basicAuth({
|
|
48
50
|
users,
|
|
49
51
|
challenge: true,
|
|
@@ -51,6 +53,87 @@ if(Object.keys(users) > 0) {
|
|
|
51
53
|
}))
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
function resolveRetentionMs(option) {
|
|
57
|
+
const value = config[option]
|
|
58
|
+
if(Number.isInteger(value)) return value
|
|
59
|
+
if(value === true && Number.isInteger(config.retentionMs)) return config.retentionMs
|
|
60
|
+
if(value !== false && Number.isInteger(config.retentionMs)) return config.retentionMs
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function shouldClear(option) {
|
|
65
|
+
const value = config[option]
|
|
66
|
+
if(value === false) return false
|
|
67
|
+
if(Number.isInteger(value)) return true
|
|
68
|
+
if(value === true) return true
|
|
69
|
+
if(Number.isInteger(config.retentionMs)) return true
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveOpLogRetentionMs() {
|
|
74
|
+
if(Number.isInteger(config.opLogRetentionMs)) return config.opLogRetentionMs
|
|
75
|
+
if(config.clearOpLogs === true && Number.isInteger(config.retentionMs)) return config.retentionMs
|
|
76
|
+
if(Number.isInteger(config.clearOpLogs)) return config.clearOpLogs
|
|
77
|
+
return DEFAULT_OP_LOG_RETENTION_MS
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveEventReportsRetentionMs() {
|
|
81
|
+
if(Number.isInteger(config.clearEventReports)) return config.clearEventReports
|
|
82
|
+
return resolveRetentionMs('clearEventReports')
|
|
83
|
+
?? resolveRetentionMs('clearCommands')
|
|
84
|
+
?? resolveRetentionMs('clearTriggers')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function computeTimestampCutoff(lastTimestamp, retentionMs) {
|
|
88
|
+
if(retentionMs === null) return null
|
|
89
|
+
const date = new Date(lastTimestamp)
|
|
90
|
+
if(!Number.isInteger(date.getTime())) return null
|
|
91
|
+
return new Date(date.getTime() - retentionMs).toISOString()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function computeCutoffs({ lastEventId, lastTriggerTimestamp, lastCommandTimestamp }) {
|
|
95
|
+
const eventsRetention = resolveRetentionMs('clearEvents')
|
|
96
|
+
let eventsBefore = lastEventId
|
|
97
|
+
if(shouldClear('clearEvents') && eventsRetention !== null && lastEventId) {
|
|
98
|
+
const ts = (+(lastEventId.split(':')[0]) - eventsRetention)
|
|
99
|
+
eventsBefore = ts.toFixed(0).padStart(16, '0') + ':'
|
|
100
|
+
} else if(!shouldClear('clearEvents')) {
|
|
101
|
+
eventsBefore = null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const triggersRetention = resolveRetentionMs('clearTriggers')
|
|
105
|
+
let triggersBefore = lastTriggerTimestamp
|
|
106
|
+
if(shouldClear('clearTriggers')) {
|
|
107
|
+
triggersBefore = computeTimestampCutoff(lastTriggerTimestamp, triggersRetention)
|
|
108
|
+
} else {
|
|
109
|
+
triggersBefore = null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const commandsRetention = resolveRetentionMs('clearCommands')
|
|
113
|
+
let commandsBefore = lastCommandTimestamp
|
|
114
|
+
if(shouldClear('clearCommands')) {
|
|
115
|
+
commandsBefore = computeTimestampCutoff(lastCommandTimestamp, commandsRetention)
|
|
116
|
+
} else {
|
|
117
|
+
commandsBefore = null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const eventReportsRetention = resolveEventReportsRetentionMs()
|
|
121
|
+
let eventReportsCommandsBefore = null
|
|
122
|
+
let eventReportsTriggersBefore = null
|
|
123
|
+
if(shouldClear('clearEventReports') && eventReportsRetention !== null) {
|
|
124
|
+
eventReportsCommandsBefore = computeTimestampCutoff(lastCommandTimestamp, eventReportsRetention)
|
|
125
|
+
eventReportsTriggersBefore = computeTimestampCutoff(lastTriggerTimestamp, eventReportsRetention)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
eventsBefore,
|
|
130
|
+
triggersBefore,
|
|
131
|
+
commandsBefore,
|
|
132
|
+
eventReportsCommandsBefore,
|
|
133
|
+
eventReportsTriggersBefore,
|
|
134
|
+
opLogRetentionMs: shouldClear('clearOpLogs') ? resolveOpLogRetentionMs() : null
|
|
135
|
+
}
|
|
136
|
+
}
|
|
54
137
|
|
|
55
138
|
function doBackup() {
|
|
56
139
|
console.log("DOING BACKUP!")
|
|
@@ -72,38 +155,55 @@ function doBackup() {
|
|
|
72
155
|
console.log("Last trigger timestamp:", lastTriggerTimestamp)
|
|
73
156
|
console.log("Last command timestamp:", lastCommandTimestamp)
|
|
74
157
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if(
|
|
81
|
-
|
|
82
|
-
const old = new Date(date.getTime() - config.clearTriggers)
|
|
83
|
-
console.log("Clear triggers before", old)
|
|
84
|
-
lastTriggerTimestamp = old.toISOString()
|
|
158
|
+
const cutoffs = computeCutoffs({ lastEventId, lastTriggerTimestamp, lastCommandTimestamp })
|
|
159
|
+
|
|
160
|
+
if(cutoffs.eventsBefore) console.log("Clear events before", new Date(+(cutoffs.eventsBefore.split(':')[0])))
|
|
161
|
+
if(cutoffs.triggersBefore) console.log("Clear triggers before", cutoffs.triggersBefore)
|
|
162
|
+
if(cutoffs.commandsBefore) console.log("Clear commands before", cutoffs.commandsBefore)
|
|
163
|
+
if(cutoffs.eventReportsCommandsBefore) {
|
|
164
|
+
console.log("Clear eventReports (commands) before", cutoffs.eventReportsCommandsBefore)
|
|
85
165
|
}
|
|
86
|
-
if(
|
|
87
|
-
|
|
88
|
-
const old = new Date(date.getTime() - config.clearCommands)
|
|
89
|
-
console.log("Clear commands before", old)
|
|
90
|
-
lastCommandTimestamp = old.toISOString()
|
|
166
|
+
if(cutoffs.eventReportsTriggersBefore) {
|
|
167
|
+
console.log("Clear eventReports (triggers) before", cutoffs.eventReportsTriggersBefore)
|
|
91
168
|
}
|
|
92
169
|
|
|
93
170
|
console.log("====== CLEARING:")
|
|
94
|
-
console.log("Last remaining event id:",
|
|
95
|
-
console.log("Last remaining trigger timestamp:",
|
|
96
|
-
console.log("Last remaining command timestamp:",
|
|
97
|
-
|
|
98
|
-
if(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
171
|
+
console.log("Last remaining event id:", cutoffs.eventsBefore)
|
|
172
|
+
console.log("Last remaining trigger timestamp:", cutoffs.triggersBefore)
|
|
173
|
+
console.log("Last remaining command timestamp:", cutoffs.commandsBefore)
|
|
174
|
+
|
|
175
|
+
if(shouldClear('clearEventReports')) {
|
|
176
|
+
await backupLog('clearEventReports start')
|
|
177
|
+
await removeOldEventReports(
|
|
178
|
+
cutoffs.eventReportsCommandsBefore,
|
|
179
|
+
cutoffs.eventReportsTriggersBefore,
|
|
180
|
+
{ delay: 10 }
|
|
181
|
+
)
|
|
182
|
+
await backupLog('clearEventReports done')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if(shouldClear('clearCommands') && cutoffs.commandsBefore) {
|
|
186
|
+
await removeOldCommands(cutoffs.commandsBefore, { delay: 10 })
|
|
187
|
+
}
|
|
188
|
+
if(shouldClear('clearTriggers') && cutoffs.triggersBefore) {
|
|
189
|
+
await removeOldTriggers(cutoffs.triggersBefore, { delay: 10 })
|
|
190
|
+
}
|
|
191
|
+
if(shouldClear('clearEvents') && cutoffs.eventsBefore) {
|
|
192
|
+
await removeOldEvents(cutoffs.eventsBefore)
|
|
193
|
+
}
|
|
194
|
+
if(shouldClear('clearOpLogs') && cutoffs.opLogRetentionMs !== null) {
|
|
195
|
+
await removeOldOpLogs(cutoffs.opLogRetentionMs)
|
|
196
|
+
}
|
|
102
197
|
})
|
|
103
198
|
}
|
|
104
|
-
currentBackup.promise.then(
|
|
199
|
+
currentBackup.promise.then(async () => {
|
|
105
200
|
console.log("BACKUP CREATED!")
|
|
106
|
-
fs.promises.writeFile(backupsDir + '/latest.txt', filename+'.tar.gz')
|
|
201
|
+
await fs.promises.writeFile(backupsDir + '/latest.txt', filename+'.tar.gz')
|
|
202
|
+
currentBackup = null
|
|
203
|
+
}).catch(async err => {
|
|
204
|
+
const detail = err && err.stack ? err.stack : String(err)
|
|
205
|
+
console.error("BACKUP FAILED:", err)
|
|
206
|
+
await backupLog(`backup failed: ${detail}`)
|
|
107
207
|
currentBackup = null
|
|
108
208
|
})
|
|
109
209
|
return currentBackup
|
|
@@ -114,7 +214,7 @@ expressApp.get('/backup/doBackup', async (req, res) => {
|
|
|
114
214
|
res.status(200).send('Backup in progress: ' + currentBackup.filename)
|
|
115
215
|
return
|
|
116
216
|
}
|
|
117
|
-
await doBackup()
|
|
217
|
+
await queue.add(() => doBackup())
|
|
118
218
|
res.status(200).send('Creating backup: '+currentBackup.filename)
|
|
119
219
|
})
|
|
120
220
|
|
|
@@ -195,7 +295,7 @@ definition.afterStart(() => {
|
|
|
195
295
|
console.log('Service init backup:')
|
|
196
296
|
await queue.add(() => doBackup())
|
|
197
297
|
console.log('Service init backup completed')
|
|
198
|
-
},
|
|
298
|
+
}, TEN_MINUTES)
|
|
199
299
|
|
|
200
300
|
setInterval(async () => {
|
|
201
301
|
console.log('Daily backup:')
|
|
@@ -204,4 +304,4 @@ definition.afterStart(() => {
|
|
|
204
304
|
}, TWENTY_FOUR_HOURS)
|
|
205
305
|
})
|
|
206
306
|
|
|
207
|
-
})
|
|
307
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/backup-service",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.213",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
"author": "Michał Łaszczewski <michal@emikse.com>",
|
|
10
10
|
"license": "BSD-3-Clause",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@live-change/db-client": "^0.9.
|
|
13
|
-
"@live-change/framework": "^0.9.
|
|
12
|
+
"@live-change/db-client": "^0.9.213",
|
|
13
|
+
"@live-change/framework": "^0.9.213",
|
|
14
14
|
"express": "^4.18.2",
|
|
15
15
|
"express-basic-auth": "^1.2.1",
|
|
16
16
|
"fs-extra": "^11.1.1",
|
|
17
|
-
"p-queue": "8.1.
|
|
17
|
+
"p-queue": "^8.1.1",
|
|
18
18
|
"progress-stream": "^2.0.0"
|
|
19
19
|
},
|
|
20
|
-
"gitHead": "
|
|
20
|
+
"gitHead": "1e22d761822df56c327242bccb3d7aed57631146",
|
|
21
21
|
"type": "module"
|
|
22
22
|
}
|