autobee 2.3.1 → 2.4.0
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/README.md +13 -0
- package/index.js +56 -2
- package/lib/fast-forward.js +113 -1
- package/lib/migrations.js +1 -0
- package/lib/writers.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,6 +81,7 @@ Options:
|
|
|
81
81
|
optimistic: true, // allow optimistic writes from unknown writers
|
|
82
82
|
isTrusted (key, reference) {}, // do we trust this writer, see Fast-forward
|
|
83
83
|
mostRecentTrusted (target, reference) {}, // the head we vouch for, see Fast-forward
|
|
84
|
+
ackThreshold: 32, // flushes we may fall behind before acking, see Acking
|
|
84
85
|
fastForward: {} // see Fast-forward
|
|
85
86
|
}
|
|
86
87
|
```
|
|
@@ -162,6 +163,10 @@ Hint that a new writer core is available at `key` with at least `length` entries
|
|
|
162
163
|
|
|
163
164
|
Rotate the local writer to a different key. The new writer takes over as the active oplog.
|
|
164
165
|
|
|
166
|
+
#### `db.setAcking(acking, [options])`
|
|
167
|
+
|
|
168
|
+
Override acking, which is otherwise driven by `isTrusted`, see Acking. `options.threshold` defaults to the current threshold.
|
|
169
|
+
|
|
165
170
|
#### `views = db.views()`
|
|
166
171
|
|
|
167
172
|
Returns the current system and view core positions. Used for replication coordination.
|
|
@@ -270,6 +275,14 @@ Fast-forward onto an oplog head, ignoring the usual distance and conservative ch
|
|
|
270
275
|
|
|
271
276
|
Pass `{ timeout }` to bound the reads, so a head nobody can serve fails instead of hanging.
|
|
272
277
|
|
|
278
|
+
### Acking
|
|
279
|
+
|
|
280
|
+
Peers fast-forward onto the heads of writers they trust, so a trusted writer that has nothing to say still has to stamp its view progress into its own oplog.
|
|
281
|
+
|
|
282
|
+
Acking does that: an empty node is appended whenever the local writer falls `ackThreshold` (default `64`) flushes behind the system.
|
|
283
|
+
|
|
284
|
+
It is enabled for exactly the writers `isTrusted` accepts. The local key is judged when the db opens, after a fast-forward, and after the local writer rotates, so a writer that becomes trusted starts acking without being told to. `setAcking` overrides it until the next of those points.
|
|
285
|
+
|
|
273
286
|
### Encryption
|
|
274
287
|
|
|
275
288
|
Pass an `encryptionKey` to encrypt all writer cores and the view at rest.
|
package/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const UpdateChanges = require('./lib/updates.js')
|
|
|
25
25
|
const migrations = require('./lib/migrations.js')
|
|
26
26
|
|
|
27
27
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
28
|
+
const DEFAULT_ACK_THRESHOLD = 32
|
|
28
29
|
const INTERRUPT = new Error('Apply interrupted')
|
|
29
30
|
|
|
30
31
|
module.exports = class Autobee extends ReadyResource {
|
|
@@ -112,6 +113,9 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
112
113
|
this._localSystemStart = 0
|
|
113
114
|
this._localSystemLength = 0
|
|
114
115
|
this._localFlushes = 0
|
|
116
|
+
this._acking = false
|
|
117
|
+
this._ackThreshold = handlers.ackThreshold || DEFAULT_ACK_THRESHOLD
|
|
118
|
+
this._ackFlushes = -1
|
|
115
119
|
this._localViewStart = 0
|
|
116
120
|
this._localViewLength = 0
|
|
117
121
|
|
|
@@ -193,6 +197,18 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
193
197
|
}
|
|
194
198
|
}
|
|
195
199
|
|
|
200
|
+
setAcking(acking, { threshold = this._ackThreshold } = {}) {
|
|
201
|
+
this._acking = acking !== false && threshold > 0
|
|
202
|
+
this._ackThreshold = threshold
|
|
203
|
+
|
|
204
|
+
if (this._acking) this.bumpSoon()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async _updateAcking() {
|
|
208
|
+
if (this._interrupting) return
|
|
209
|
+
this.setAcking(await this.trusted.isTrusted(this.local.key, this.view))
|
|
210
|
+
}
|
|
211
|
+
|
|
196
212
|
// network free: only a migration needs peers, so ready() awaits the full
|
|
197
213
|
// state boot unless one is pending and defers it to the background if so -
|
|
198
214
|
// anything needing the booted system must wait on the boot guard
|
|
@@ -468,6 +484,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
468
484
|
|
|
469
485
|
this._bootGuard.exit()
|
|
470
486
|
|
|
487
|
+
await this._updateAcking()
|
|
488
|
+
|
|
471
489
|
this.bumpSoon()
|
|
472
490
|
|
|
473
491
|
return this._bootGuard.ready()
|
|
@@ -672,7 +690,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
672
690
|
break // revaluate conditions...
|
|
673
691
|
}
|
|
674
692
|
|
|
675
|
-
if (
|
|
693
|
+
if (await this._bumpPendingWriters()) {
|
|
694
|
+
this._needsUpdate = true
|
|
695
|
+
continue
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (!(await this._appendAck())) break
|
|
676
699
|
this._needsUpdate = true
|
|
677
700
|
}
|
|
678
701
|
|
|
@@ -922,6 +945,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
922
945
|
await this.writers.rotateLocalWriter(this.local)
|
|
923
946
|
|
|
924
947
|
this._updateLocalCore = null
|
|
948
|
+
this._ackFlushes = -1
|
|
925
949
|
|
|
926
950
|
this.local.setUserData('referrer', this.key)
|
|
927
951
|
if (this.encryptionKey) {
|
|
@@ -931,6 +955,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
931
955
|
await this.bootstrap.setUserData('autobee/local', this.local.key)
|
|
932
956
|
await oldLocal.close()
|
|
933
957
|
|
|
958
|
+
await this._updateAcking()
|
|
959
|
+
|
|
934
960
|
// done, soft restart
|
|
935
961
|
this.emit('rotate-local-writer')
|
|
936
962
|
}
|
|
@@ -1101,6 +1127,30 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1101
1127
|
}
|
|
1102
1128
|
}
|
|
1103
1129
|
|
|
1130
|
+
async _appendAck() {
|
|
1131
|
+
if (!this._acking) return false
|
|
1132
|
+
if (!this.writers.writable) return false
|
|
1133
|
+
|
|
1134
|
+
if (this.writers.localWriter.pending !== null) return false
|
|
1135
|
+
|
|
1136
|
+
if ((await this._flushesBehind()) < this._ackThreshold) return false
|
|
1137
|
+
|
|
1138
|
+
const links = this.system.getLinks(this.local.key)
|
|
1139
|
+
const t = Math.max(this._now(), this.system.timestamp)
|
|
1140
|
+
|
|
1141
|
+
this.writers.appendLocal(null, t, { start: 0, end: 0 }, links, false, null)
|
|
1142
|
+
return true
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
async _flushesBehind() {
|
|
1146
|
+
if (this._ackFlushes === -1) {
|
|
1147
|
+
const latest = await this.writers.getLatestLocalOplog()
|
|
1148
|
+
this._ackFlushes = latest && latest.views ? latest.views.flushes : this.system.flushes
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return this.system.flushes - this._ackFlushes
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1104
1154
|
async _bumpPendingWriters({ local = false } = {}) {
|
|
1105
1155
|
if (!local && this._catchupMigratedNodes !== null) {
|
|
1106
1156
|
const updated = await this._bumpMigratedWriters()
|
|
@@ -1358,7 +1408,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1358
1408
|
this._needsUpdate = true
|
|
1359
1409
|
}
|
|
1360
1410
|
|
|
1361
|
-
await this.writers.flushLocal({
|
|
1411
|
+
const flushed = await this.writers.flushLocal({
|
|
1362
1412
|
flushes: this._localFlushes,
|
|
1363
1413
|
system: {
|
|
1364
1414
|
key: this.system.bee.context.local.key,
|
|
@@ -1372,6 +1422,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1372
1422
|
}
|
|
1373
1423
|
})
|
|
1374
1424
|
|
|
1425
|
+
if (flushed) this._ackFlushes = this._localFlushes
|
|
1426
|
+
|
|
1375
1427
|
this._localSystemStart = this.system.bee.context.local.length
|
|
1376
1428
|
this._localSystemLength = 0
|
|
1377
1429
|
this._localFlushes = this.system.flushes
|
|
@@ -1481,6 +1533,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1481
1533
|
await this._update(changes, true)
|
|
1482
1534
|
await this._storeBoot()
|
|
1483
1535
|
|
|
1536
|
+
await this._updateAcking()
|
|
1537
|
+
|
|
1484
1538
|
this.stats.fastForwards++
|
|
1485
1539
|
this.emit('move-to', to, from)
|
|
1486
1540
|
this.ff.resolve({ to, from })
|
package/lib/fast-forward.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
const safetyCatch = require('safety-catch')
|
|
2
|
+
const b4a = require('b4a')
|
|
2
3
|
const Hypercore = require('hypercore')
|
|
3
4
|
|
|
4
5
|
const System = require('./system.js')
|
|
5
6
|
const migrations = require('./migrations.js')
|
|
7
|
+
const { LEGACY_AUTOBASE_VERSION, LEGACY_OPLOG_VERSION } = require('./constants.js')
|
|
6
8
|
|
|
7
9
|
const DEFAULT_OP_TIMEOUT = 5_000
|
|
8
10
|
const MIN_FF_GAP = 32
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
class FastForward {
|
|
11
13
|
constructor(auto, head, { timeout = DEFAULT_OP_TIMEOUT } = {}) {
|
|
12
14
|
this.auto = auto
|
|
13
15
|
|
|
@@ -162,6 +164,10 @@ module.exports = class FastForward {
|
|
|
162
164
|
|
|
163
165
|
await this.system.boot(this.head, { timeout: this.timeout })
|
|
164
166
|
|
|
167
|
+
if (this.system.version <= LEGACY_AUTOBASE_VERSION) {
|
|
168
|
+
if (!(await this._runLegacy())) return null
|
|
169
|
+
}
|
|
170
|
+
|
|
165
171
|
const promises = []
|
|
166
172
|
|
|
167
173
|
// ensure local key is locally available always
|
|
@@ -202,6 +208,68 @@ module.exports = class FastForward {
|
|
|
202
208
|
return migrations.coreLength(core, this.timeout)
|
|
203
209
|
}
|
|
204
210
|
|
|
211
|
+
async _runLegacy() {
|
|
212
|
+
const resolved = await this._resolveLegacy()
|
|
213
|
+
if (resolved === null) return false
|
|
214
|
+
|
|
215
|
+
const { head, autobee } = resolved
|
|
216
|
+
|
|
217
|
+
if (autobee !== null) {
|
|
218
|
+
try {
|
|
219
|
+
this.head = batchToHead(autobee.op.views.system)
|
|
220
|
+
await this.system.boot(this.head, { timeout: this.timeout })
|
|
221
|
+
if (this.system.version > LEGACY_AUTOBASE_VERSION) return true
|
|
222
|
+
} catch (err) {
|
|
223
|
+
safetyCatch(err)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.head = head
|
|
228
|
+
await this.system.boot(this.head, { timeout: this.timeout })
|
|
229
|
+
|
|
230
|
+
return true
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async _resolveLegacy() {
|
|
234
|
+
let head = { key: this.head.key, length: this.head.length }
|
|
235
|
+
const seen = new Set()
|
|
236
|
+
|
|
237
|
+
// bound at 5 hops for safety
|
|
238
|
+
let hops = 5
|
|
239
|
+
while (true) {
|
|
240
|
+
seen.add(b4a.toString(head.key, 'hex'))
|
|
241
|
+
|
|
242
|
+
const core = this.auto.store.get({ key: head.key, encryption: null })
|
|
243
|
+
this.cores.push(core)
|
|
244
|
+
await core.ready()
|
|
245
|
+
|
|
246
|
+
await core.setEncryption(this.auto.getSystemEncryption())
|
|
247
|
+
|
|
248
|
+
// successors are resolved by key only - wait for the replicated length
|
|
249
|
+
if (!head.length) head.length = await migrations.coreLength(core, this.timeout)
|
|
250
|
+
if (!head.length || this.destroyed) return null
|
|
251
|
+
|
|
252
|
+
const info = await migrations.readLegacySystemInfo(core, head.length, {
|
|
253
|
+
timeout: this.timeout,
|
|
254
|
+
activeRequests: this.activeRequests
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
const { legacy, autobee } = await findNewestSystem(this, info)
|
|
258
|
+
if (this.destroyed) return null
|
|
259
|
+
|
|
260
|
+
// an autobee candidate ends the crawl: it may be behind, but once we are
|
|
261
|
+
// on autobee the ordinary fast-forward carries us to the newest view
|
|
262
|
+
if (autobee !== null) return { head, autobee }
|
|
263
|
+
|
|
264
|
+
if (hops-- > 0 && legacy !== null && !seen.has(b4a.toString(legacy, 'hex'))) {
|
|
265
|
+
head = { key: legacy, length: 0 }
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { head, autobee: null }
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
205
273
|
// cancels every read in flight and fails any that come after
|
|
206
274
|
clearRequests(err = null) {
|
|
207
275
|
Hypercore.destroyRequests(this.activeRequests, err)
|
|
@@ -215,6 +283,50 @@ module.exports = class FastForward {
|
|
|
215
283
|
}
|
|
216
284
|
}
|
|
217
285
|
|
|
286
|
+
module.exports = FastForward
|
|
287
|
+
|
|
288
|
+
async function findNewestSystem(ff, info) {
|
|
289
|
+
let autobee = null
|
|
290
|
+
let legacy = null
|
|
291
|
+
let flushes = -1
|
|
292
|
+
|
|
293
|
+
for (const idx of info.indexers) {
|
|
294
|
+
const core = ff.auto.openCore(idx.key)
|
|
295
|
+
ff.cores.push(core)
|
|
296
|
+
await core.ready()
|
|
297
|
+
|
|
298
|
+
const length = await migrations.coreLength(core, ff.timeout)
|
|
299
|
+
if (!length || length <= idx.length || ff.destroyed) continue
|
|
300
|
+
|
|
301
|
+
const oplog = await FastForward.flushHead(
|
|
302
|
+
ff.auto,
|
|
303
|
+
{ key: idx.key, length },
|
|
304
|
+
{ timeout: ff.timeout }
|
|
305
|
+
)
|
|
306
|
+
if (oplog === null || !oplog.op.views) continue
|
|
307
|
+
|
|
308
|
+
// this indexer has already moved to autobee: follow it rather than
|
|
309
|
+
// digging a legacy stamp out of its history
|
|
310
|
+
if (oplog.op.version > LEGACY_OPLOG_VERSION) {
|
|
311
|
+
if (autobee === null || oplog.op.views.flushes > autobee.op.views.flushes) {
|
|
312
|
+
autobee = oplog
|
|
313
|
+
}
|
|
314
|
+
continue
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const sys = oplog.op.views.system
|
|
318
|
+
if (!sys || !sys.key) continue
|
|
319
|
+
|
|
320
|
+
// follow whichever indexer has checkpointed furthest
|
|
321
|
+
if (oplog.op.views.flushes > flushes) {
|
|
322
|
+
flushes = oplog.op.views.flushes
|
|
323
|
+
legacy = sys.key
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return { legacy, autobee }
|
|
328
|
+
}
|
|
329
|
+
|
|
218
330
|
function batchToHead(b) {
|
|
219
331
|
return {
|
|
220
332
|
key: b.key,
|
package/lib/migrations.js
CHANGED
package/lib/writers.js
CHANGED
|
@@ -122,11 +122,12 @@ class ActiveWriters {
|
|
|
122
122
|
const w = this.localWriter
|
|
123
123
|
|
|
124
124
|
// only consume the queue when a flush will actually write
|
|
125
|
-
if (w.pending === null || w.processed === 0) return
|
|
125
|
+
if (w.pending === null || w.processed === 0) return false
|
|
126
126
|
|
|
127
127
|
const trusted = await this.auto.trusted.mostRecentTrusted(this.auto._workingView.view, null)
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
await w.flush(views, trusted)
|
|
130
|
+
return true
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
async refresh() {
|