@optimystic/db-p2p 1.0.0-beta.3 → 1.1.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.
Files changed (234) hide show
  1. package/dist/src/cluster/block-transfer-service.d.ts +73 -1
  2. package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
  3. package/dist/src/cluster/block-transfer-service.js +70 -3
  4. package/dist/src/cluster/block-transfer-service.js.map +1 -1
  5. package/dist/src/cluster/block-transfer.d.ts +16 -1
  6. package/dist/src/cluster/block-transfer.d.ts.map +1 -1
  7. package/dist/src/cluster/block-transfer.js +73 -72
  8. package/dist/src/cluster/block-transfer.js.map +1 -1
  9. package/dist/src/cluster/client.d.ts +4 -6
  10. package/dist/src/cluster/client.d.ts.map +1 -1
  11. package/dist/src/cluster/client.js +8 -13
  12. package/dist/src/cluster/client.js.map +1 -1
  13. package/dist/src/cluster/cluster-policy.d.ts +2 -1
  14. package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
  15. package/dist/src/cluster/cluster-policy.js.map +1 -1
  16. package/dist/src/cluster/cluster-repo.d.ts +155 -9
  17. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  18. package/dist/src/cluster/cluster-repo.js +446 -58
  19. package/dist/src/cluster/cluster-repo.js.map +1 -1
  20. package/dist/src/cluster/commit-proof.js +2 -2
  21. package/dist/src/cluster/race-resolution.d.ts +51 -7
  22. package/dist/src/cluster/race-resolution.d.ts.map +1 -1
  23. package/dist/src/cluster/race-resolution.js +71 -7
  24. package/dist/src/cluster/race-resolution.js.map +1 -1
  25. package/dist/src/cluster/rebalance-monitor.d.ts +67 -5
  26. package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
  27. package/dist/src/cluster/rebalance-monitor.js +120 -26
  28. package/dist/src/cluster/rebalance-monitor.js.map +1 -1
  29. package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
  30. package/dist/src/cluster/spread-on-churn.js +34 -47
  31. package/dist/src/cluster/spread-on-churn.js.map +1 -1
  32. package/dist/src/cohort-topic/host.d.ts.map +1 -1
  33. package/dist/src/cohort-topic/host.js +8 -6
  34. package/dist/src/cohort-topic/host.js.map +1 -1
  35. package/dist/src/cohort-topic/membership-source.js +1 -1
  36. package/dist/src/cohort-topic/membership-source.js.map +1 -1
  37. package/dist/src/cohort-topic/peer-codec.d.ts.map +1 -1
  38. package/dist/src/cohort-topic/peer-codec.js +5 -2
  39. package/dist/src/cohort-topic/peer-codec.js.map +1 -1
  40. package/dist/src/cohort-topic/stream-util.d.ts +24 -3
  41. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  42. package/dist/src/cohort-topic/stream-util.js +36 -3
  43. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  44. package/dist/src/cohort-topic/topic-router.d.ts +9 -0
  45. package/dist/src/cohort-topic/topic-router.d.ts.map +1 -1
  46. package/dist/src/cohort-topic/topic-router.js +12 -2
  47. package/dist/src/cohort-topic/topic-router.js.map +1 -1
  48. package/dist/src/dispute/client.d.ts.map +1 -1
  49. package/dist/src/dispute/client.js +15 -3
  50. package/dist/src/dispute/client.js.map +1 -1
  51. package/dist/src/index.d.ts +5 -0
  52. package/dist/src/index.d.ts.map +1 -1
  53. package/dist/src/index.js +5 -0
  54. package/dist/src/index.js.map +1 -1
  55. package/dist/src/libp2p-key-network.d.ts +78 -17
  56. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  57. package/dist/src/libp2p-key-network.js +234 -147
  58. package/dist/src/libp2p-key-network.js.map +1 -1
  59. package/dist/src/libp2p-node-base.d.ts +35 -1
  60. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  61. package/dist/src/libp2p-node-base.js +151 -26
  62. package/dist/src/libp2p-node-base.js.map +1 -1
  63. package/dist/src/libp2p-node-rn.d.ts +2 -2
  64. package/dist/src/libp2p-node-rn.d.ts.map +1 -1
  65. package/dist/src/libp2p-node-rn.js.map +1 -1
  66. package/dist/src/libp2p-node.d.ts +2 -2
  67. package/dist/src/libp2p-node.d.ts.map +1 -1
  68. package/dist/src/libp2p-node.js.map +1 -1
  69. package/dist/src/logger.d.ts.map +1 -1
  70. package/dist/src/logger.js +6 -1
  71. package/dist/src/logger.js.map +1 -1
  72. package/dist/src/matchmaking/query-transport.d.ts +3 -4
  73. package/dist/src/matchmaking/query-transport.d.ts.map +1 -1
  74. package/dist/src/matchmaking/query-transport.js +9 -9
  75. package/dist/src/matchmaking/query-transport.js.map +1 -1
  76. package/dist/src/network/network-manager-service.d.ts +10 -5
  77. package/dist/src/network/network-manager-service.d.ts.map +1 -1
  78. package/dist/src/network/network-manager-service.js +11 -2
  79. package/dist/src/network/network-manager-service.js.map +1 -1
  80. package/dist/src/network/relay-reservation.d.ts +170 -0
  81. package/dist/src/network/relay-reservation.d.ts.map +1 -0
  82. package/dist/src/network/relay-reservation.js +527 -0
  83. package/dist/src/network/relay-reservation.js.map +1 -0
  84. package/dist/src/pend-validation.d.ts +3 -2
  85. package/dist/src/pend-validation.d.ts.map +1 -1
  86. package/dist/src/pend-validation.js +3 -2
  87. package/dist/src/pend-validation.js.map +1 -1
  88. package/dist/src/protocol-client.d.ts +8 -1
  89. package/dist/src/protocol-client.d.ts.map +1 -1
  90. package/dist/src/protocol-client.js +11 -0
  91. package/dist/src/protocol-client.js.map +1 -1
  92. package/dist/src/reactivity/forwarder-host.d.ts +2 -1
  93. package/dist/src/reactivity/forwarder-host.d.ts.map +1 -1
  94. package/dist/src/reactivity/forwarder-host.js +2 -1
  95. package/dist/src/reactivity/forwarder-host.js.map +1 -1
  96. package/dist/src/reactivity/origination-manager.d.ts +3 -3
  97. package/dist/src/reactivity/origination-manager.js +3 -3
  98. package/dist/src/reactivity/recover-transport.d.ts +22 -14
  99. package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
  100. package/dist/src/reactivity/recover-transport.js +32 -19
  101. package/dist/src/reactivity/recover-transport.js.map +1 -1
  102. package/dist/src/reactivity/subscription-manager.d.ts +2 -2
  103. package/dist/src/reactivity/topic-bytes.d.ts +6 -7
  104. package/dist/src/reactivity/topic-bytes.d.ts.map +1 -1
  105. package/dist/src/reactivity/topic-bytes.js +8 -9
  106. package/dist/src/reactivity/topic-bytes.js.map +1 -1
  107. package/dist/src/repo/client.d.ts +5 -2
  108. package/dist/src/repo/client.d.ts.map +1 -1
  109. package/dist/src/repo/client.js +49 -40
  110. package/dist/src/repo/client.js.map +1 -1
  111. package/dist/src/repo/cluster-coordinator.d.ts +96 -11
  112. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  113. package/dist/src/repo/cluster-coordinator.js +164 -25
  114. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  115. package/dist/src/repo/coordinator-repo.d.ts +240 -122
  116. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  117. package/dist/src/repo/coordinator-repo.js +627 -473
  118. package/dist/src/repo/coordinator-repo.js.map +1 -1
  119. package/dist/src/repo/i-under-replication-ledger.d.ts +87 -0
  120. package/dist/src/repo/i-under-replication-ledger.d.ts.map +1 -0
  121. package/dist/src/repo/i-under-replication-ledger.js +2 -0
  122. package/dist/src/repo/i-under-replication-ledger.js.map +1 -0
  123. package/dist/src/repo/kv-under-replication-ledger.d.ts +91 -0
  124. package/dist/src/repo/kv-under-replication-ledger.d.ts.map +1 -0
  125. package/dist/src/repo/kv-under-replication-ledger.js +287 -0
  126. package/dist/src/repo/kv-under-replication-ledger.js.map +1 -0
  127. package/dist/src/repo/responsibility.d.ts +37 -0
  128. package/dist/src/repo/responsibility.d.ts.map +1 -0
  129. package/dist/src/repo/responsibility.js +37 -0
  130. package/dist/src/repo/responsibility.js.map +1 -0
  131. package/dist/src/repo/service.d.ts +44 -15
  132. package/dist/src/repo/service.d.ts.map +1 -1
  133. package/dist/src/repo/service.js +64 -27
  134. package/dist/src/repo/service.js.map +1 -1
  135. package/dist/src/repo/stuck-reservation.d.ts +127 -0
  136. package/dist/src/repo/stuck-reservation.d.ts.map +1 -0
  137. package/dist/src/repo/stuck-reservation.js +179 -0
  138. package/dist/src/repo/stuck-reservation.js.map +1 -0
  139. package/dist/src/repo/under-replication-drain.d.ts +208 -0
  140. package/dist/src/repo/under-replication-drain.d.ts.map +1 -0
  141. package/dist/src/repo/under-replication-drain.js +461 -0
  142. package/dist/src/repo/under-replication-drain.js.map +1 -0
  143. package/dist/src/rn.d.ts +5 -0
  144. package/dist/src/rn.d.ts.map +1 -1
  145. package/dist/src/rn.js +5 -0
  146. package/dist/src/rn.js.map +1 -1
  147. package/dist/src/storage/block-latch.d.ts +3 -2
  148. package/dist/src/storage/block-latch.d.ts.map +1 -1
  149. package/dist/src/storage/block-latch.js +7 -4
  150. package/dist/src/storage/block-latch.js.map +1 -1
  151. package/dist/src/storage/block-storage.d.ts +45 -3
  152. package/dist/src/storage/block-storage.d.ts.map +1 -1
  153. package/dist/src/storage/block-storage.js +156 -9
  154. package/dist/src/storage/block-storage.js.map +1 -1
  155. package/dist/src/storage/i-block-storage.d.ts +57 -5
  156. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  157. package/dist/src/storage/i-block-storage.js.map +1 -1
  158. package/dist/src/storage/pending-claim.d.ts +167 -0
  159. package/dist/src/storage/pending-claim.d.ts.map +1 -0
  160. package/dist/src/storage/pending-claim.js +156 -0
  161. package/dist/src/storage/pending-claim.js.map +1 -0
  162. package/dist/src/storage/raw-store-codec.d.ts +13 -0
  163. package/dist/src/storage/raw-store-codec.d.ts.map +1 -1
  164. package/dist/src/storage/raw-store-codec.js +24 -3
  165. package/dist/src/storage/raw-store-codec.js.map +1 -1
  166. package/dist/src/storage/restoration-coordinator.d.ts.map +1 -1
  167. package/dist/src/storage/restoration-coordinator.js +2 -1
  168. package/dist/src/storage/restoration-coordinator.js.map +1 -1
  169. package/dist/src/storage/ring-shift-coordinator.d.ts.map +1 -1
  170. package/dist/src/storage/ring-shift-coordinator.js +3 -3
  171. package/dist/src/storage/ring-shift-coordinator.js.map +1 -1
  172. package/dist/src/storage/storage-repo.d.ts +122 -5
  173. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  174. package/dist/src/storage/storage-repo.js +245 -68
  175. package/dist/src/storage/storage-repo.js.map +1 -1
  176. package/dist/src/storage/struct.d.ts +58 -0
  177. package/dist/src/storage/struct.d.ts.map +1 -1
  178. package/dist/src/testing/mesh-harness.d.ts +77 -11
  179. package/dist/src/testing/mesh-harness.d.ts.map +1 -1
  180. package/dist/src/testing/mesh-harness.js +226 -100
  181. package/dist/src/testing/mesh-harness.js.map +1 -1
  182. package/package.json +2 -2
  183. package/readme.md +27 -3
  184. package/src/cluster/block-transfer-service.ts +132 -4
  185. package/src/cluster/block-transfer.ts +79 -84
  186. package/src/cluster/client.ts +115 -119
  187. package/src/cluster/cluster-policy.ts +2 -1
  188. package/src/cluster/cluster-repo.ts +493 -65
  189. package/src/cluster/commit-proof.ts +2 -2
  190. package/src/cluster/race-resolution.ts +76 -7
  191. package/src/cluster/rebalance-monitor.ts +677 -539
  192. package/src/cluster/spread-on-churn.ts +34 -54
  193. package/src/cohort-topic/host.ts +8 -6
  194. package/src/cohort-topic/membership-source.ts +1 -1
  195. package/src/cohort-topic/peer-codec.ts +34 -31
  196. package/src/cohort-topic/stream-util.ts +39 -4
  197. package/src/cohort-topic/topic-router.ts +12 -2
  198. package/src/dispute/client.ts +17 -7
  199. package/src/index.ts +64 -59
  200. package/src/libp2p-key-network.ts +1377 -1260
  201. package/src/libp2p-node-base.ts +186 -27
  202. package/src/libp2p-node-rn.ts +31 -30
  203. package/src/libp2p-node.ts +37 -36
  204. package/src/logger.ts +6 -1
  205. package/src/matchmaking/query-transport.ts +9 -9
  206. package/src/network/network-manager-service.ts +16 -5
  207. package/src/network/relay-reservation.ts +636 -0
  208. package/src/pend-validation.ts +3 -2
  209. package/src/protocol-client.ts +13 -1
  210. package/src/reactivity/forwarder-host.ts +2 -1
  211. package/src/reactivity/origination-manager.ts +3 -3
  212. package/src/reactivity/recover-transport.ts +39 -22
  213. package/src/reactivity/subscription-manager.ts +2 -2
  214. package/src/reactivity/topic-bytes.ts +35 -37
  215. package/src/repo/client.ts +172 -173
  216. package/src/repo/cluster-coordinator.ts +183 -25
  217. package/src/repo/coordinator-repo.ts +656 -506
  218. package/src/repo/i-under-replication-ledger.ts +89 -0
  219. package/src/repo/kv-under-replication-ledger.ts +302 -0
  220. package/src/repo/responsibility.ts +48 -0
  221. package/src/repo/service.ts +76 -39
  222. package/src/repo/stuck-reservation.ts +224 -0
  223. package/src/repo/under-replication-drain.ts +582 -0
  224. package/src/rn.ts +74 -69
  225. package/src/storage/block-latch.ts +7 -4
  226. package/src/storage/block-storage.ts +161 -10
  227. package/src/storage/i-block-storage.ts +60 -5
  228. package/src/storage/pending-claim.ts +200 -0
  229. package/src/storage/raw-store-codec.ts +26 -3
  230. package/src/storage/restoration-coordinator.ts +212 -211
  231. package/src/storage/ring-shift-coordinator.ts +271 -271
  232. package/src/storage/storage-repo.ts +280 -72
  233. package/src/storage/struct.ts +58 -0
  234. package/src/testing/mesh-harness.ts +293 -112
@@ -1,13 +1,14 @@
1
1
  import { pipe } from 'it-pipe';
2
2
  import { decode as lpDecode, encode as lpEncode } from 'it-length-prefixed';
3
- import { blockIdsForTransforms } from '@optimystic/db-core';
4
- import { peersEqual } from '../peer-utils.js';
3
+ import { LruMap, blockIdsForTransforms, routingKeyForBlock } from '@optimystic/db-core';
4
+ import { peerIdFromString } from '@libp2p/peer-id';
5
5
  import { encodePeers } from './redirect.js';
6
6
  import { MAX_BLOCK_MESSAGE_BYTES } from '../protocol-limits.js';
7
7
  import { createLogger } from '../logger.js';
8
8
  import { publishableAddrsForPeer } from '../peer-address-book.js';
9
9
  import { createInboundStreamAuthorization } from '../inbound-authorization.js';
10
10
  import { registerProtocolHandler } from '../network/register-protocol-handler.js';
11
+ import { RESPONSIBILITY_TTL_MS } from './responsibility.js';
11
12
  export function repoService(init = {}) {
12
13
  return (components) => new RepoService(components, init);
13
14
  }
@@ -28,7 +29,7 @@ export class RepoService {
28
29
  * The libp2p node, injected post-construction by the node wiring (see
29
30
  * libp2p-node-base.ts, mirroring how `networkManager`/`fret` receive theirs).
30
31
  * The libp2p `components.libp2p` proxy does NOT reliably resolve from inside a
31
- * service at request time, so the redirect path resolves the network manager,
32
+ * service at request time, so the redirect path resolves the key network,
32
33
  * self identity, and connection addrs through this explicitly-set reference.
33
34
  */
34
35
  libp2pRef;
@@ -40,6 +41,16 @@ export class RepoService {
40
41
  * ingress point, instead of keeping it filterable as the single `peer-address-book` namespace.
41
42
  */
42
43
  addressLog;
44
+ /**
45
+ * Per block key, the responsible peer ids the key network last named: one cohort lookup per block per
46
+ * minute on the inbound path, instead of one per request on top of the coordinator's own.
47
+ *
48
+ * NOTE: same TTL as `CoordinatorRepo`'s responsibility cache, deliberately not the same cache — the two
49
+ * sit on either side of the served-repo boundary. Their verdicts can therefore disagree about a block
50
+ * only inside the minute after its cohort changes; a request this check wrongly lets through is refused
51
+ * by the coordinator, and one it wrongly redirects is followed by the client to a member that serves it.
52
+ */
53
+ responsibleIds = new LruMap(1000);
43
54
  constructor(components, init = {}) {
44
55
  this.components = components;
45
56
  const computed = init.protocol ?? (init.protocolPrefix ?? '/db-p2p') + '/repo/1.0.0';
@@ -88,10 +99,15 @@ export class RepoService {
88
99
  await this.components.registrar.unhandle(this.protocol);
89
100
  this.running = false;
90
101
  }
91
- getNetworkManager() {
92
- if (this.components.networkManager)
93
- return this.components.networkManager;
94
- return this.getLibp2p()?.services?.networkManager;
102
+ /**
103
+ * The key network the redirect check asks. Absent only before the node attaches it, which happens after
104
+ * `start()`; a request in that window is handled locally, where the served-repo proxy has no
105
+ * coordinator to hand it to yet either.
106
+ */
107
+ getKeyNetwork() {
108
+ if (this.components.keyNetwork)
109
+ return this.components.keyNetwork;
110
+ return this.getLibp2p()?.keyNetwork;
95
111
  }
96
112
  getSelfId() {
97
113
  if (this.components.peerId)
@@ -126,7 +142,7 @@ export class RepoService {
126
142
  * - pend → blockIdsForTransforms(transforms)[0]
127
143
  * - cancel → actionRef.blockIds[0]
128
144
  * - commit → blockIds[0] (CoordinatorRepo.commit anchors consensus on
129
- * getClusterSize(blockIds[0]) / executeClusterTransaction(blockIds[0]) and guards
145
+ * resolveCohort(blockIds[0]) / executeClusterTransaction(blockIds[0]) and guards
130
146
  * with verifyResponsibility(blockIds) — NOT tailId; for a per-block commit batch
131
147
  * whose blockIds[0] !== tailId, keying on tailId redirected the commit to the
132
148
  * collection tail's cluster, which then fails verifyResponsibility for the non-tail block.)
@@ -153,37 +169,58 @@ export class RepoService {
153
169
  /**
154
170
  * Check if this node should redirect the request for a given key.
155
171
  * Returns a RedirectPayload if not responsible, null if should handle locally.
156
- * Also attaches cluster info to the message for downstream use.
172
+ * Also attaches the responsible peer ids to the message for downstream use.
173
+ *
174
+ * The responsible set comes from the node's own key network — `findCluster` on the block's routing
175
+ * key, the same call and the same bytes the writer's transactor and this node's `CoordinatorRepo` use —
176
+ * so a correctly routed request is never redirected, including on machines shared by several networks,
177
+ * where FRET's raw cohort would include peers that do not serve this one.
178
+ *
179
+ * A lookup that THROWS splits by operation: a `get` is handled locally (a read is best-effort, and the
180
+ * coordinator flags what it could not confirm); every other operation propagates the error, which
181
+ * aborts the stream so the writer excludes this peer and re-picks — the posture `CoordinatorRepo` takes
182
+ * on the same fault.
157
183
  */
158
184
  async checkRedirect(blockKey, opName, message) {
159
- const nm = this.getNetworkManager();
160
- if (!nm)
185
+ const keyNetwork = this.getKeyNetwork();
186
+ if (!keyNetwork)
161
187
  return null;
162
- // Pass the RAW encoded block-key bytes to getCluster. getCluster hashes
163
- // internally (hashKey == sha256), so the responsible-set coordinate becomes
164
- // hashKey(encode(blockKey)) — identical to how the cluster coordinator
165
- // derives it (ClusterCoordinator.getClusterForBlock → findCluster(encode(blockId))).
166
- // Pre-hashing here would double-hash (hashKey(sha256(encode(blockKey)))), placing
167
- // the cohort at an unrelated ring coordinate and redirecting requests the
168
- // coordinator legitimately routed to this peer.
169
- const key = new TextEncoder().encode(blockKey);
170
- const cluster = await nm.getCluster(key);
171
- message.cluster = cluster.map((p) => p.toString?.() ?? String(p));
172
- const selfId = this.getSelfId();
188
+ let cluster;
189
+ try {
190
+ cluster = await this.responsiblePeerIds(keyNetwork, blockKey);
191
+ }
192
+ catch (err) {
193
+ this.log.error('redirect lookup failed op=%s blockKey=%s - %e', opName, blockKey, err);
194
+ if (opName === 'get')
195
+ return null;
196
+ throw err;
197
+ }
198
+ ;
199
+ message.cluster = [...cluster];
200
+ const selfId = this.getSelfId()?.toString();
173
201
  if (!selfId)
174
202
  return null;
175
- const isMember = cluster.some((p) => peersEqual(p, selfId));
203
+ const isMember = cluster.includes(selfId);
176
204
  const smallMesh = cluster.length < this.responsibilityK;
177
205
  if (!smallMesh && !isMember) {
178
- const peers = cluster.filter((p) => !peersEqual(p, selfId));
179
206
  this.log('redirect op=%s blockKey=%s cluster=%d', opName, blockKey, cluster.length);
180
- return encodePeers(await Promise.all(peers.map(async (pid) => ({
181
- id: pid.toString(),
182
- addrs: await this.getPeerAddrs(pid)
207
+ return encodePeers(await Promise.all(cluster.map(async (id) => ({
208
+ id,
209
+ addrs: await this.getPeerAddrs(peerIdFromString(id))
183
210
  }))));
184
211
  }
185
212
  return null;
186
213
  }
214
+ /** The block's responsible peer ids, memoized per block key for {@link RESPONSIBILITY_TTL_MS}. A throw is never memoized. */
215
+ async responsiblePeerIds(keyNetwork, blockKey) {
216
+ const now = Date.now();
217
+ const memo = this.responsibleIds.get(blockKey);
218
+ if (memo && memo.expires > now)
219
+ return memo.peerIds;
220
+ const peerIds = Object.keys(await keyNetwork.findCluster(routingKeyForBlock(blockKey)));
221
+ this.responsibleIds.set(blockKey, { peerIds, expires: now + RESPONSIBILITY_TTL_MS });
222
+ return peerIds;
223
+ }
187
224
  /**
188
225
  * Handle incoming streams on the repo protocol
189
226
  */
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/repo/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/repo/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ import { type ActionId, type BlockId } from "@optimystic/db-core";
2
+ /**
3
+ * How many DISTINCT later actions one unchanged holder must refuse on a block before the refusals are
4
+ * named as a stuck reservation rather than as an ordinary lost race.
5
+ *
6
+ * **Why a count of distinct actions and not something else.** Elapsed time answers the wrong question
7
+ * — a slow writer is not a stuck one, and a holder legitimately keeps its reservation for as long as
8
+ * its own commit takes. A raw refusal count answers the wrong question too: a single writer retrying
9
+ * a lost race produces a run of refusals under ONE action id (see {@link StuckReservationWatch.refused}).
10
+ * What no healthy holder can produce is an unbounded stream of *different* writers all losing to it,
11
+ * because a healthy holder's reservation lasts one pend-to-commit window.
12
+ *
13
+ * **Why 8.** The bound to clear is how many distinct actions can honestly be refused inside one such
14
+ * window. Measured on the in-process mesh, in the healthy-contention arm of
15
+ * `test/stuck-reservation-named.spec.ts`: a holder that pends, is raced by other writers, and then
16
+ * commits refuses **2** distinct actions per episode — the two rivals — and the count resets on every
17
+ * holder change. `concurrent-diary-append-acknowledgement.spec.ts` races three writers at one diary
18
+ * and cannot exceed that either, for the same reason: at most (writers - 1) rivals can lose to one
19
+ * winner. 8 is four times the measured healthy figure, and it is a floor a genuinely stuck block
20
+ * clears trivially (the field instance refused hundreds).
21
+ *
22
+ * **The bound stated exactly.** It is distinct SYNC CYCLES, not distinct writers: one writer that
23
+ * exhausts a sync's retry budget and is re-driven by its caller mints a fresh id for the next cycle,
24
+ * so it can contribute more than one. That does not widen the window much — a cycle only ends in
25
+ * exhaustion after `DefaultMaxAttempts` (10) attempts of backoff, roughly 21s (see the exhaustion
26
+ * NOTE in `Collection.syncAttempts`), so a lone writer needs a holder to keep the block for upwards
27
+ * of two and a half minutes before it reaches 8 by itself, which is not a healthy holder.
28
+ *
29
+ * **What the margin does NOT cover, stated honestly.** A block with more than 8 distinct writers
30
+ * racing it inside a single pend-to-commit round trip could reach 8 with a perfectly healthy holder.
31
+ * That is a diagnostic false positive on a log line and nothing else — this counter never refuses,
32
+ * expires, or deletes anything (see {@link StuckReservationTracker}) — and the remedy if
33
+ * a deployment ever hits it is to raise this number, not to add a control path. Raising it costs
34
+ * detection latency on low-traffic blocks, which need this many distinct write ATTEMPTS before the
35
+ * condition can be named at all.
36
+ */
37
+ export declare const STUCK_RESERVATION_DISTINCT_ACTIONS = 8;
38
+ /**
39
+ * The stuck-reservation wording: written for an operator reading logs, in the same register as
40
+ * `cohortTooSmallMessage` and `soleHolderMessage` in `coordinator-repo.ts` — what is stuck, what will
41
+ * and will not clear it, and what to do next.
42
+ *
43
+ * The claim is deliberately about the RESERVATION, not about the writer's intent: this node cannot
44
+ * see whether the holding process is alive, only that it has held the block across enough unrelated
45
+ * later actions that no retry is going to win. So the line says what is provable (the block accepts
46
+ * no writes while this record stands, and nothing on the node removes it) and points at the one check
47
+ * that settles the rest.
48
+ */
49
+ export declare function stuckReservationMessage(holders: readonly ActionId[], refusedActions: number): string;
50
+ /**
51
+ * One episode that just crossed the threshold — what the caller logs, verbatim, under its own tag.
52
+ * The ids an operator needs to grep for and to cancel are kept as data beside the prose so a log
53
+ * search finds the block and the action without parsing English.
54
+ */
55
+ export interface StuckReservationEpisode {
56
+ blockId: BlockId;
57
+ holdingActionIds: readonly ActionId[];
58
+ distinctRefusedActions: number;
59
+ message: string;
60
+ }
61
+ /**
62
+ * Count pending-conflict refusals against the holder(s) of each block they name, and say ONCE — in
63
+ * words, at the moment it becomes provable — when a block is wedged behind a reservation that is not
64
+ * going to clear.
65
+ *
66
+ * **Why this needs saying at all.** Every individual refusal is indistinguishable from an ordinary
67
+ * lost race, which is a normal and healthy event, so the logs of a permanently wedged block read
68
+ * exactly like the logs of a busy one. Finding the difference otherwise means noticing that the SAME
69
+ * rival action id keeps appearing across unrelated writers for as long as the process lives — a
70
+ * pattern nothing points at, and one that cost a downstream project several tickets and weeks to
71
+ * re-derive from raw traces. The node has the fact in hand at every refusal; this makes it sayable.
72
+ *
73
+ * **The signal, and the two things that are NOT the signal.** The discriminator is repetition
74
+ * against an unchanged holder — see {@link STUCK_RESERVATION_DISTINCT_ACTIONS} for why distinct
75
+ * refused actions is the right counter and for the measured threshold. Two cheaper-looking tests
76
+ * were tried and do not work: the members' in-memory reservation table
77
+ * (`ClusterMember.activeTransactions`) clears the moment a rival's pend reaches consensus, so a
78
+ * perfectly healthy rival inside its pend-to-commit window is absent from it too and absence there
79
+ * says nothing; and "the block already passed this pending record's revision" catches a different
80
+ * orphan class entirely — in the verified instance the wedged block sat at revision 1 while the
81
+ * orphaned record was for revision 2, still nominally promotable. (That second class is now handled
82
+ * at its own site — a record the incoming writer has built on no longer refuses at all, see
83
+ * `isReservationAgainst` — so what reaches this counter is a holder whose change the block has not
84
+ * taken: one whose slot every later writer still wants, or one past the base every later writer
85
+ * declares, such as an abandoned action's record on a block no later write touched.)
86
+ *
87
+ * **Never a control path.** This classifies and reports; it never refuses, expires, or deletes
88
+ * anything. Deciding when a durable pending record may be removed is precisely the hard problem
89
+ * backlog `debt-unpromotable-pending-records-need-a-sweep` exists for — deleting a live reservation
90
+ * is worse than the leak — and a counter accurate enough for a log line is not evidence enough to
91
+ * destroy state.
92
+ *
93
+ * **Two instances, two vantage points.** The coordinator keeps one (`CoordinatorRepo.noteStuckReservation`),
94
+ * fed by cohort-wide `held`-answered refusals whose holder its OWN storage corroborates, and every
95
+ * member keeps one (`ClusterMember.validatePendOperations`), fed by its own `held` votes — so a
96
+ * reservation only remote members hold is named by those members, where the record actually lives,
97
+ * rather than going unnamed at a coordinator that cannot see it.
98
+ *
99
+ * LRU-bounded: an eviction under more than `capacity` conflicted blocks loses an episode's say-once
100
+ * flag, so the line can repeat once for that block — bounded duplication, far cheaper than the
101
+ * unbounded silence it replaces.
102
+ */
103
+ export declare class StuckReservationTracker {
104
+ private readonly watches;
105
+ constructor(capacity?: number);
106
+ /**
107
+ * Count one refusal of `refusedActionId` against the rival holder(s) of each block in `rivalsByBlock`.
108
+ * Returns the highest distinct-refusal count any of these blocks has now reached — for a
109
+ * classification line to carry, saturating at the threshold once an episode has been reported,
110
+ * since the ids are dropped at that point — and every episode that crossed the threshold on this
111
+ * refusal, which the caller must log.
112
+ */
113
+ note(rivalsByBlock: ReadonlyMap<BlockId, readonly ActionId[]>, refusedActionId: ActionId): {
114
+ highest: number;
115
+ named: StuckReservationEpisode[];
116
+ };
117
+ /**
118
+ * Forget any episode recorded for these blocks, optionally only when `holderActionId` is one of
119
+ * the actions that episode named. Called from the events the message names as the cures — a write
120
+ * the block accepted, a cancel for the holding action — and it is LRU hygiene rather than
121
+ * behaviour: a later wedge is named by a DIFFERENT holder, which {@link note}'s holder comparison
122
+ * already treats as a new episode whether the old entry is still there or not. Kept because a
123
+ * settled episode holding an LRU slot can only evict a live one.
124
+ */
125
+ forget(blockIds: readonly BlockId[], holderActionId?: ActionId): void;
126
+ }
127
+ //# sourceMappingURL=stuck-reservation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stuck-reservation.d.ts","sourceRoot":"","sources":["../../../src/repo/stuck-reservation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAgC1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,kCAAkC,IAAI,CAAC;AAUpD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAiBpG;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,qBAAa,uBAAuB;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;gBAEpD,QAAQ,SAAO;IAI3B;;;;;;OAMG;IACH,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC,EAAE,eAAe,EAAE,QAAQ,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,uBAAuB,EAAE,CAAA;KAAE;IAkChJ;;;;;;;OAOG;IACH,MAAM,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,cAAc,CAAC,EAAE,QAAQ,GAAG,IAAI;CAMrE"}
@@ -0,0 +1,179 @@
1
+ import { LruMap } from "@optimystic/db-core";
2
+ /**
3
+ * How many DISTINCT later actions one unchanged holder must refuse on a block before the refusals are
4
+ * named as a stuck reservation rather than as an ordinary lost race.
5
+ *
6
+ * **Why a count of distinct actions and not something else.** Elapsed time answers the wrong question
7
+ * — a slow writer is not a stuck one, and a holder legitimately keeps its reservation for as long as
8
+ * its own commit takes. A raw refusal count answers the wrong question too: a single writer retrying
9
+ * a lost race produces a run of refusals under ONE action id (see {@link StuckReservationWatch.refused}).
10
+ * What no healthy holder can produce is an unbounded stream of *different* writers all losing to it,
11
+ * because a healthy holder's reservation lasts one pend-to-commit window.
12
+ *
13
+ * **Why 8.** The bound to clear is how many distinct actions can honestly be refused inside one such
14
+ * window. Measured on the in-process mesh, in the healthy-contention arm of
15
+ * `test/stuck-reservation-named.spec.ts`: a holder that pends, is raced by other writers, and then
16
+ * commits refuses **2** distinct actions per episode — the two rivals — and the count resets on every
17
+ * holder change. `concurrent-diary-append-acknowledgement.spec.ts` races three writers at one diary
18
+ * and cannot exceed that either, for the same reason: at most (writers - 1) rivals can lose to one
19
+ * winner. 8 is four times the measured healthy figure, and it is a floor a genuinely stuck block
20
+ * clears trivially (the field instance refused hundreds).
21
+ *
22
+ * **The bound stated exactly.** It is distinct SYNC CYCLES, not distinct writers: one writer that
23
+ * exhausts a sync's retry budget and is re-driven by its caller mints a fresh id for the next cycle,
24
+ * so it can contribute more than one. That does not widen the window much — a cycle only ends in
25
+ * exhaustion after `DefaultMaxAttempts` (10) attempts of backoff, roughly 21s (see the exhaustion
26
+ * NOTE in `Collection.syncAttempts`), so a lone writer needs a holder to keep the block for upwards
27
+ * of two and a half minutes before it reaches 8 by itself, which is not a healthy holder.
28
+ *
29
+ * **What the margin does NOT cover, stated honestly.** A block with more than 8 distinct writers
30
+ * racing it inside a single pend-to-commit round trip could reach 8 with a perfectly healthy holder.
31
+ * That is a diagnostic false positive on a log line and nothing else — this counter never refuses,
32
+ * expires, or deletes anything (see {@link StuckReservationTracker}) — and the remedy if
33
+ * a deployment ever hits it is to raise this number, not to add a control path. Raising it costs
34
+ * detection latency on low-traffic blocks, which need this many distinct write ATTEMPTS before the
35
+ * condition can be named at all.
36
+ */
37
+ export const STUCK_RESERVATION_DISTINCT_ACTIONS = 8;
38
+ /** Whether two sorted holder lists name the same reservation — i.e. whether a refusal continues an
39
+ * existing episode or starts a new one. Both sides come from the same sort, so this is a plain
40
+ * element-wise comparison; a block normally has exactly one holder, since a member's own pend refuses
41
+ * a second one (`ClusterMember.validatePendOperations`). */
42
+ function sameHolders(a, b) {
43
+ return a.length === b.length && a.every((id, i) => id === b[i]);
44
+ }
45
+ /**
46
+ * The stuck-reservation wording: written for an operator reading logs, in the same register as
47
+ * `cohortTooSmallMessage` and `soleHolderMessage` in `coordinator-repo.ts` — what is stuck, what will
48
+ * and will not clear it, and what to do next.
49
+ *
50
+ * The claim is deliberately about the RESERVATION, not about the writer's intent: this node cannot
51
+ * see whether the holding process is alive, only that it has held the block across enough unrelated
52
+ * later actions that no retry is going to win. So the line says what is provable (the block accepts
53
+ * no writes while this record stands, and nothing on the node removes it) and points at the one check
54
+ * that settles the rest.
55
+ */
56
+ export function stuckReservationMessage(holders, refusedActions) {
57
+ const held = holders.join(', ');
58
+ return `This block is WEDGED BEHIND A PENDING WRITE THAT IS NOT COMPLETING, and retrying will never ` +
59
+ `clear it: action(s) ${held} reserved the block and have now refused ${refusedActions} DISTINCT, ` +
60
+ `unrelated later actions. Each of those refusals on its own looks exactly like an ordinary ` +
61
+ `optimistic-concurrency loss, which is normal and healthy — the repetition is what is not. A ` +
62
+ `healthy rival holds a block only for its own pend-to-commit window and then releases it by ` +
63
+ `committing or cancelling; a reservation that keeps refusing NEW writers is holding the block ` +
64
+ `against every writer on every machine, and each of them loses again identically. EXACTLY TWO ` +
65
+ `THINGS CLEAR IT: a cancel for action(s) ${held} on this block (route it through the cohort so ` +
66
+ `every member drops the record), or that same action's own commit landing. Nothing on the node ` +
67
+ `expires it — there is no sweep for abandoned pending records — so until one of those two happens ` +
68
+ `the block takes NO writes while continuing to serve reads and to look healthy in every other ` +
69
+ `respect. The usual cause is a writer that went away between a failed or half-applied commit and ` +
70
+ `the cancel it owed, so check whether whatever ran ${held} still exists before cancelling on its ` +
71
+ `behalf. This line is a diagnosis and nothing more: this node does not expire, refuse, or delete ` +
72
+ `the record on the strength of it.`;
73
+ }
74
+ /**
75
+ * Count pending-conflict refusals against the holder(s) of each block they name, and say ONCE — in
76
+ * words, at the moment it becomes provable — when a block is wedged behind a reservation that is not
77
+ * going to clear.
78
+ *
79
+ * **Why this needs saying at all.** Every individual refusal is indistinguishable from an ordinary
80
+ * lost race, which is a normal and healthy event, so the logs of a permanently wedged block read
81
+ * exactly like the logs of a busy one. Finding the difference otherwise means noticing that the SAME
82
+ * rival action id keeps appearing across unrelated writers for as long as the process lives — a
83
+ * pattern nothing points at, and one that cost a downstream project several tickets and weeks to
84
+ * re-derive from raw traces. The node has the fact in hand at every refusal; this makes it sayable.
85
+ *
86
+ * **The signal, and the two things that are NOT the signal.** The discriminator is repetition
87
+ * against an unchanged holder — see {@link STUCK_RESERVATION_DISTINCT_ACTIONS} for why distinct
88
+ * refused actions is the right counter and for the measured threshold. Two cheaper-looking tests
89
+ * were tried and do not work: the members' in-memory reservation table
90
+ * (`ClusterMember.activeTransactions`) clears the moment a rival's pend reaches consensus, so a
91
+ * perfectly healthy rival inside its pend-to-commit window is absent from it too and absence there
92
+ * says nothing; and "the block already passed this pending record's revision" catches a different
93
+ * orphan class entirely — in the verified instance the wedged block sat at revision 1 while the
94
+ * orphaned record was for revision 2, still nominally promotable. (That second class is now handled
95
+ * at its own site — a record the incoming writer has built on no longer refuses at all, see
96
+ * `isReservationAgainst` — so what reaches this counter is a holder whose change the block has not
97
+ * taken: one whose slot every later writer still wants, or one past the base every later writer
98
+ * declares, such as an abandoned action's record on a block no later write touched.)
99
+ *
100
+ * **Never a control path.** This classifies and reports; it never refuses, expires, or deletes
101
+ * anything. Deciding when a durable pending record may be removed is precisely the hard problem
102
+ * backlog `debt-unpromotable-pending-records-need-a-sweep` exists for — deleting a live reservation
103
+ * is worse than the leak — and a counter accurate enough for a log line is not evidence enough to
104
+ * destroy state.
105
+ *
106
+ * **Two instances, two vantage points.** The coordinator keeps one (`CoordinatorRepo.noteStuckReservation`),
107
+ * fed by cohort-wide `held`-answered refusals whose holder its OWN storage corroborates, and every
108
+ * member keeps one (`ClusterMember.validatePendOperations`), fed by its own `held` votes — so a
109
+ * reservation only remote members hold is named by those members, where the record actually lives,
110
+ * rather than going unnamed at a coordinator that cannot see it.
111
+ *
112
+ * LRU-bounded: an eviction under more than `capacity` conflicted blocks loses an episode's say-once
113
+ * flag, so the line can repeat once for that block — bounded duplication, far cheaper than the
114
+ * unbounded silence it replaces.
115
+ */
116
+ export class StuckReservationTracker {
117
+ watches;
118
+ constructor(capacity = 1000) {
119
+ this.watches = new LruMap(capacity);
120
+ }
121
+ /**
122
+ * Count one refusal of `refusedActionId` against the rival holder(s) of each block in `rivalsByBlock`.
123
+ * Returns the highest distinct-refusal count any of these blocks has now reached — for a
124
+ * classification line to carry, saturating at the threshold once an episode has been reported,
125
+ * since the ids are dropped at that point — and every episode that crossed the threshold on this
126
+ * refusal, which the caller must log.
127
+ */
128
+ note(rivalsByBlock, refusedActionId) {
129
+ let highest = 0;
130
+ const named = [];
131
+ for (const [blockId, rivals] of rivalsByBlock) {
132
+ const holders = [...new Set(rivals)].sort();
133
+ const prior = this.watches.get(blockId);
134
+ // A different holder set is a DIFFERENT episode — the block changed hands, which is the
135
+ // healthy cycle — so the count starts over and the new holder gets its own chance to speak.
136
+ const watch = prior !== undefined && sameHolders(prior.holders, holders)
137
+ ? prior
138
+ : { holders, refused: new Set(), reported: false };
139
+ if (watch !== prior)
140
+ this.watches.set(blockId, watch);
141
+ if (watch.reported) {
142
+ highest = Math.max(highest, STUCK_RESERVATION_DISTINCT_ACTIONS);
143
+ continue;
144
+ }
145
+ watch.refused.add(refusedActionId);
146
+ highest = Math.max(highest, watch.refused.size);
147
+ if (watch.refused.size < STUCK_RESERVATION_DISTINCT_ACTIONS)
148
+ continue;
149
+ named.push({
150
+ blockId,
151
+ holdingActionIds: holders,
152
+ distinctRefusedActions: watch.refused.size,
153
+ message: stuckReservationMessage(holders, watch.refused.size)
154
+ });
155
+ watch.reported = true;
156
+ // Said once per episode: from here the flag alone suppresses, and the ids have done their
157
+ // work (their count is in the line above), so drop them rather than growing a set for the
158
+ // unbounded remainder of a permanent condition.
159
+ watch.refused.clear();
160
+ }
161
+ return { highest, named };
162
+ }
163
+ /**
164
+ * Forget any episode recorded for these blocks, optionally only when `holderActionId` is one of
165
+ * the actions that episode named. Called from the events the message names as the cures — a write
166
+ * the block accepted, a cancel for the holding action — and it is LRU hygiene rather than
167
+ * behaviour: a later wedge is named by a DIFFERENT holder, which {@link note}'s holder comparison
168
+ * already treats as a new episode whether the old entry is still there or not. Kept because a
169
+ * settled episode holding an LRU slot can only evict a live one.
170
+ */
171
+ forget(blockIds, holderActionId) {
172
+ for (const blockId of blockIds) {
173
+ if (holderActionId !== undefined && !this.watches.peek(blockId)?.holders.includes(holderActionId))
174
+ continue;
175
+ this.watches.delete(blockId);
176
+ }
177
+ }
178
+ }
179
+ //# sourceMappingURL=stuck-reservation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stuck-reservation.js","sourceRoot":"","sources":["../../../src/repo/stuck-reservation.ts"],"names":[],"mappings":"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"}