@effect/cluster 0.50.6 → 0.52.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 (232) hide show
  1. package/RunnerStorage/package.json +6 -0
  2. package/SqlRunnerStorage/package.json +6 -0
  3. package/dist/cjs/ClusterError.js +2 -24
  4. package/dist/cjs/ClusterError.js.map +1 -1
  5. package/dist/cjs/ClusterMetrics.js +13 -15
  6. package/dist/cjs/ClusterMetrics.js.map +1 -1
  7. package/dist/cjs/ClusterSchema.js +17 -2
  8. package/dist/cjs/ClusterSchema.js.map +1 -1
  9. package/dist/cjs/ClusterWorkflowEngine.js +50 -83
  10. package/dist/cjs/ClusterWorkflowEngine.js.map +1 -1
  11. package/dist/cjs/Entity.js +1 -13
  12. package/dist/cjs/Entity.js.map +1 -1
  13. package/dist/cjs/EntityAddress.js +9 -1
  14. package/dist/cjs/EntityAddress.js.map +1 -1
  15. package/dist/cjs/EntityId.js +7 -1
  16. package/dist/cjs/EntityId.js.map +1 -1
  17. package/dist/cjs/EntityProxy.js +1 -1
  18. package/dist/cjs/EntityProxy.js.map +1 -1
  19. package/dist/cjs/HttpRunner.js +69 -43
  20. package/dist/cjs/HttpRunner.js.map +1 -1
  21. package/dist/cjs/MessageStorage.js +64 -16
  22. package/dist/cjs/MessageStorage.js.map +1 -1
  23. package/dist/cjs/Runner.js +3 -3
  24. package/dist/cjs/Runner.js.map +1 -1
  25. package/dist/cjs/RunnerAddress.js +7 -0
  26. package/dist/cjs/RunnerAddress.js.map +1 -1
  27. package/dist/cjs/RunnerHealth.js +91 -32
  28. package/dist/cjs/RunnerHealth.js.map +1 -1
  29. package/dist/cjs/RunnerServer.js +38 -24
  30. package/dist/cjs/RunnerServer.js.map +1 -1
  31. package/dist/cjs/RunnerStorage.js +100 -0
  32. package/dist/cjs/RunnerStorage.js.map +1 -0
  33. package/dist/cjs/Runners.js +18 -22
  34. package/dist/cjs/Runners.js.map +1 -1
  35. package/dist/cjs/ShardId.js +17 -7
  36. package/dist/cjs/ShardId.js.map +1 -1
  37. package/dist/cjs/Sharding.js +444 -320
  38. package/dist/cjs/Sharding.js.map +1 -1
  39. package/dist/cjs/ShardingConfig.js +10 -14
  40. package/dist/cjs/ShardingConfig.js.map +1 -1
  41. package/dist/cjs/Snowflake.js +1 -1
  42. package/dist/cjs/SocketRunner.js +1 -1
  43. package/dist/cjs/SocketRunner.js.map +1 -1
  44. package/dist/cjs/SqlMessageStorage.js +22 -28
  45. package/dist/cjs/SqlMessageStorage.js.map +1 -1
  46. package/dist/cjs/SqlRunnerStorage.js +375 -0
  47. package/dist/cjs/SqlRunnerStorage.js.map +1 -0
  48. package/dist/cjs/index.js +5 -15
  49. package/dist/cjs/internal/entityManager.js +42 -23
  50. package/dist/cjs/internal/entityManager.js.map +1 -1
  51. package/dist/dts/ClusterError.d.ts +0 -22
  52. package/dist/dts/ClusterError.d.ts.map +1 -1
  53. package/dist/dts/ClusterMetrics.d.ts +4 -14
  54. package/dist/dts/ClusterMetrics.d.ts.map +1 -1
  55. package/dist/dts/ClusterSchema.d.ts +9 -1
  56. package/dist/dts/ClusterSchema.d.ts.map +1 -1
  57. package/dist/dts/ClusterWorkflowEngine.d.ts.map +1 -1
  58. package/dist/dts/Entity.d.ts +3 -14
  59. package/dist/dts/Entity.d.ts.map +1 -1
  60. package/dist/dts/EntityAddress.d.ts +11 -0
  61. package/dist/dts/EntityAddress.d.ts.map +1 -1
  62. package/dist/dts/EntityId.d.ts +5 -0
  63. package/dist/dts/EntityId.d.ts.map +1 -1
  64. package/dist/dts/EntityProxy.d.ts +5 -6
  65. package/dist/dts/EntityProxy.d.ts.map +1 -1
  66. package/dist/dts/HttpRunner.d.ts +48 -25
  67. package/dist/dts/HttpRunner.d.ts.map +1 -1
  68. package/dist/dts/MessageStorage.d.ts +13 -5
  69. package/dist/dts/MessageStorage.d.ts.map +1 -1
  70. package/dist/dts/Runner.d.ts +4 -4
  71. package/dist/dts/Runner.d.ts.map +1 -1
  72. package/dist/dts/RunnerAddress.d.ts +5 -0
  73. package/dist/dts/RunnerAddress.d.ts.map +1 -1
  74. package/dist/dts/RunnerHealth.d.ts +24 -16
  75. package/dist/dts/RunnerHealth.d.ts.map +1 -1
  76. package/dist/dts/RunnerServer.d.ts +5 -4
  77. package/dist/dts/RunnerServer.d.ts.map +1 -1
  78. package/dist/dts/{ShardStorage.d.ts → RunnerStorage.d.ts} +41 -54
  79. package/dist/dts/RunnerStorage.d.ts.map +1 -0
  80. package/dist/dts/Runners.d.ts +15 -11
  81. package/dist/dts/Runners.d.ts.map +1 -1
  82. package/dist/dts/ShardId.d.ts +1 -1
  83. package/dist/dts/ShardId.d.ts.map +1 -1
  84. package/dist/dts/Sharding.d.ts +20 -10
  85. package/dist/dts/Sharding.d.ts.map +1 -1
  86. package/dist/dts/ShardingConfig.d.ts +40 -14
  87. package/dist/dts/ShardingConfig.d.ts.map +1 -1
  88. package/dist/dts/SocketRunner.d.ts +4 -3
  89. package/dist/dts/SocketRunner.d.ts.map +1 -1
  90. package/dist/dts/SqlMessageStorage.d.ts +2 -3
  91. package/dist/dts/SqlMessageStorage.d.ts.map +1 -1
  92. package/dist/dts/SqlRunnerStorage.d.ts +40 -0
  93. package/dist/dts/SqlRunnerStorage.d.ts.map +1 -0
  94. package/dist/dts/index.d.ts +4 -24
  95. package/dist/dts/index.d.ts.map +1 -1
  96. package/dist/esm/ClusterError.js +0 -21
  97. package/dist/esm/ClusterError.js.map +1 -1
  98. package/dist/esm/ClusterMetrics.js +12 -14
  99. package/dist/esm/ClusterMetrics.js.map +1 -1
  100. package/dist/esm/ClusterSchema.js +17 -2
  101. package/dist/esm/ClusterSchema.js.map +1 -1
  102. package/dist/esm/ClusterWorkflowEngine.js +50 -83
  103. package/dist/esm/ClusterWorkflowEngine.js.map +1 -1
  104. package/dist/esm/Entity.js +0 -12
  105. package/dist/esm/Entity.js.map +1 -1
  106. package/dist/esm/EntityAddress.js +7 -0
  107. package/dist/esm/EntityAddress.js.map +1 -1
  108. package/dist/esm/EntityId.js +5 -0
  109. package/dist/esm/EntityId.js.map +1 -1
  110. package/dist/esm/EntityProxy.js +2 -2
  111. package/dist/esm/EntityProxy.js.map +1 -1
  112. package/dist/esm/HttpRunner.js +62 -39
  113. package/dist/esm/HttpRunner.js.map +1 -1
  114. package/dist/esm/MessageStorage.js +65 -17
  115. package/dist/esm/MessageStorage.js.map +1 -1
  116. package/dist/esm/Runner.js +3 -3
  117. package/dist/esm/Runner.js.map +1 -1
  118. package/dist/esm/RunnerAddress.js +7 -0
  119. package/dist/esm/RunnerAddress.js.map +1 -1
  120. package/dist/esm/RunnerHealth.js +88 -30
  121. package/dist/esm/RunnerHealth.js.map +1 -1
  122. package/dist/esm/RunnerServer.js +38 -24
  123. package/dist/esm/RunnerServer.js.map +1 -1
  124. package/dist/esm/RunnerStorage.js +90 -0
  125. package/dist/esm/RunnerStorage.js.map +1 -0
  126. package/dist/esm/Runners.js +19 -23
  127. package/dist/esm/Runners.js.map +1 -1
  128. package/dist/esm/ShardId.js +16 -6
  129. package/dist/esm/ShardId.js.map +1 -1
  130. package/dist/esm/Sharding.js +447 -323
  131. package/dist/esm/Sharding.js.map +1 -1
  132. package/dist/esm/ShardingConfig.js +10 -14
  133. package/dist/esm/ShardingConfig.js.map +1 -1
  134. package/dist/esm/Snowflake.js +1 -1
  135. package/dist/esm/SocketRunner.js +1 -1
  136. package/dist/esm/SocketRunner.js.map +1 -1
  137. package/dist/esm/SqlMessageStorage.js +22 -28
  138. package/dist/esm/SqlMessageStorage.js.map +1 -1
  139. package/dist/esm/SqlRunnerStorage.js +366 -0
  140. package/dist/esm/SqlRunnerStorage.js.map +1 -0
  141. package/dist/esm/index.js +4 -24
  142. package/dist/esm/index.js.map +1 -1
  143. package/dist/esm/internal/entityManager.js +41 -22
  144. package/dist/esm/internal/entityManager.js.map +1 -1
  145. package/package.json +20 -60
  146. package/src/ClusterError.ts +0 -24
  147. package/src/ClusterMetrics.ts +12 -16
  148. package/src/ClusterSchema.ts +17 -2
  149. package/src/ClusterWorkflowEngine.ts +48 -80
  150. package/src/Entity.ts +3 -21
  151. package/src/EntityAddress.ts +10 -0
  152. package/src/EntityId.ts +6 -0
  153. package/src/EntityProxy.ts +10 -10
  154. package/src/HttpRunner.ts +132 -67
  155. package/src/MessageStorage.ts +89 -24
  156. package/src/Runner.ts +4 -4
  157. package/src/RunnerAddress.ts +8 -0
  158. package/src/RunnerHealth.ts +119 -56
  159. package/src/RunnerServer.ts +64 -47
  160. package/src/RunnerStorage.ts +218 -0
  161. package/src/Runners.ts +32 -45
  162. package/src/ShardId.ts +14 -3
  163. package/src/Sharding.ts +561 -417
  164. package/src/ShardingConfig.ts +39 -31
  165. package/src/Snowflake.ts +1 -1
  166. package/src/SocketRunner.ts +6 -4
  167. package/src/SqlMessageStorage.ts +28 -30
  168. package/src/SqlRunnerStorage.ts +537 -0
  169. package/src/index.ts +4 -29
  170. package/src/internal/entityManager.ts +45 -29
  171. package/HttpCommon/package.json +0 -6
  172. package/HttpShardManager/package.json +0 -6
  173. package/ShardManager/package.json +0 -6
  174. package/ShardStorage/package.json +0 -6
  175. package/SocketShardManager/package.json +0 -6
  176. package/SqlShardStorage/package.json +0 -6
  177. package/SynchronizedClock/package.json +0 -6
  178. package/dist/cjs/HttpCommon.js +0 -48
  179. package/dist/cjs/HttpCommon.js.map +0 -1
  180. package/dist/cjs/HttpShardManager.js +0 -139
  181. package/dist/cjs/HttpShardManager.js.map +0 -1
  182. package/dist/cjs/ShardManager.js +0 -549
  183. package/dist/cjs/ShardManager.js.map +0 -1
  184. package/dist/cjs/ShardStorage.js +0 -151
  185. package/dist/cjs/ShardStorage.js.map +0 -1
  186. package/dist/cjs/SocketShardManager.js +0 -32
  187. package/dist/cjs/SocketShardManager.js.map +0 -1
  188. package/dist/cjs/SqlShardStorage.js +0 -253
  189. package/dist/cjs/SqlShardStorage.js.map +0 -1
  190. package/dist/cjs/SynchronizedClock.js +0 -65
  191. package/dist/cjs/SynchronizedClock.js.map +0 -1
  192. package/dist/cjs/internal/shardManager.js +0 -353
  193. package/dist/cjs/internal/shardManager.js.map +0 -1
  194. package/dist/dts/HttpCommon.d.ts +0 -25
  195. package/dist/dts/HttpCommon.d.ts.map +0 -1
  196. package/dist/dts/HttpShardManager.d.ts +0 -119
  197. package/dist/dts/HttpShardManager.d.ts.map +0 -1
  198. package/dist/dts/ShardManager.d.ts +0 -459
  199. package/dist/dts/ShardManager.d.ts.map +0 -1
  200. package/dist/dts/ShardStorage.d.ts.map +0 -1
  201. package/dist/dts/SocketShardManager.d.ts +0 -17
  202. package/dist/dts/SocketShardManager.d.ts.map +0 -1
  203. package/dist/dts/SqlShardStorage.d.ts +0 -38
  204. package/dist/dts/SqlShardStorage.d.ts.map +0 -1
  205. package/dist/dts/SynchronizedClock.d.ts +0 -19
  206. package/dist/dts/SynchronizedClock.d.ts.map +0 -1
  207. package/dist/dts/internal/shardManager.d.ts +0 -2
  208. package/dist/dts/internal/shardManager.d.ts.map +0 -1
  209. package/dist/esm/HttpCommon.js +0 -38
  210. package/dist/esm/HttpCommon.js.map +0 -1
  211. package/dist/esm/HttpShardManager.js +0 -128
  212. package/dist/esm/HttpShardManager.js.map +0 -1
  213. package/dist/esm/ShardManager.js +0 -535
  214. package/dist/esm/ShardManager.js.map +0 -1
  215. package/dist/esm/ShardStorage.js +0 -141
  216. package/dist/esm/ShardStorage.js.map +0 -1
  217. package/dist/esm/SocketShardManager.js +0 -24
  218. package/dist/esm/SocketShardManager.js.map +0 -1
  219. package/dist/esm/SqlShardStorage.js +0 -244
  220. package/dist/esm/SqlShardStorage.js.map +0 -1
  221. package/dist/esm/SynchronizedClock.js +0 -57
  222. package/dist/esm/SynchronizedClock.js.map +0 -1
  223. package/dist/esm/internal/shardManager.js +0 -342
  224. package/dist/esm/internal/shardManager.js.map +0 -1
  225. package/src/HttpCommon.ts +0 -73
  226. package/src/HttpShardManager.ts +0 -273
  227. package/src/ShardManager.ts +0 -823
  228. package/src/ShardStorage.ts +0 -297
  229. package/src/SocketShardManager.ts +0 -48
  230. package/src/SqlShardStorage.ts +0 -329
  231. package/src/SynchronizedClock.ts +0 -82
  232. package/src/internal/shardManager.ts +0 -412
@@ -1,82 +0,0 @@
1
- /**
2
- * @since 1.0.0
3
- */
4
- import * as Clock from "effect/Clock"
5
- import * as Duration from "effect/Duration"
6
- import * as Effect from "effect/Effect"
7
- import * as Layer from "effect/Layer"
8
- import * as Schedule from "effect/Schedule"
9
- import type { Scope } from "effect/Scope"
10
- import { ShardManagerClient } from "./ShardManager.js"
11
-
12
- /**
13
- * @since 1.0.0
14
- * @category Constructors
15
- */
16
- export const make: (getRemoteTime: Effect.Effect<number, never, never>) => Effect.Effect<
17
- Clock.Clock,
18
- never,
19
- Scope
20
- > = Effect.fnUntraced(function*(getRemoteTime) {
21
- const clock = yield* Effect.clock
22
-
23
- let driftMillis = 0
24
- let driftNanos = BigInt(0)
25
-
26
- yield* getRemoteTime.pipe(
27
- Effect.timed,
28
- Effect.map(([duration, shardManagerTime]) => {
29
- const halfTrip = Duration.unsafeDivide(duration, 2)
30
- shardManagerTime = shardManagerTime + Duration.toMillis(halfTrip) + 1
31
- const selfTime = clock.unsafeCurrentTimeMillis()
32
- return shardManagerTime - selfTime
33
- }),
34
- Effect.replicateEffect(5),
35
- Effect.flatMap((drifts) => {
36
- drifts.sort()
37
- const drift = (driftMillis + drifts[2]) / 2
38
- driftMillis = Math.round(drift)
39
- driftNanos = BigInt(Math.round(drift * 1_000_000))
40
- return Effect.logDebug("Current drift", driftMillis)
41
- }),
42
- Effect.andThen(Effect.sleep(Duration.minutes(5))),
43
- Effect.forever,
44
- Effect.sandbox,
45
- Effect.retry(Schedule.spaced(Duration.minutes(1))),
46
- Effect.annotateLogs({
47
- package: "@effect/cluster",
48
- service: "SynchronizedClock"
49
- }),
50
- Effect.forkScoped
51
- )
52
-
53
- function unsafeCurrentTimeMillis() {
54
- return clock.unsafeCurrentTimeMillis() + driftMillis
55
- }
56
- function unsafeCurrentTimeNanos() {
57
- return clock.unsafeCurrentTimeNanos() + driftNanos
58
- }
59
-
60
- return Clock.Clock.of({
61
- [Clock.ClockTypeId]: Clock.ClockTypeId,
62
- sleep: clock.sleep,
63
- unsafeCurrentTimeMillis,
64
- unsafeCurrentTimeNanos,
65
- currentTimeMillis: Effect.sync(unsafeCurrentTimeMillis),
66
- currentTimeNanos: Effect.sync(unsafeCurrentTimeNanos)
67
- })
68
- })
69
-
70
- /**
71
- * @since 1.0.0
72
- * @category Layers
73
- */
74
- export const layer: Layer.Layer<
75
- never,
76
- never,
77
- ShardManagerClient
78
- > = Layer.unwrapScoped(Effect.gen(function*() {
79
- const shardManager = yield* ShardManagerClient
80
- const clock = yield* make(shardManager.getTime)
81
- return Layer.setClock(clock)
82
- }))
@@ -1,412 +0,0 @@
1
- import * as Arr from "effect/Array"
2
- import * as Clock from "effect/Clock"
3
- import * as Effect from "effect/Effect"
4
- import { constFalse } from "effect/Function"
5
- import * as MutableHashMap from "effect/MutableHashMap"
6
- import * as MutableHashSet from "effect/MutableHashSet"
7
- import * as Option from "effect/Option"
8
- import type { Runner } from "../Runner.js"
9
- import type { RunnerAddress } from "../RunnerAddress.js"
10
- import { RunnerHealth } from "../RunnerHealth.js"
11
- import { ShardId } from "../ShardId.js"
12
- import { ShardStorage } from "../ShardStorage.js"
13
-
14
- /** @internal */
15
- export class State {
16
- static fromStorage = Effect.fnUntraced(function*(
17
- shardsPerGroup: number
18
- ) {
19
- const storage = yield* ShardStorage
20
- const runnerHealth = yield* RunnerHealth
21
-
22
- // Fetch registered runners and shard assignments from cluster storage
23
- const storedRunners = yield* storage.getRunners
24
- const storedAssignments = yield* storage.getAssignments
25
-
26
- // Determine which runners are still alive
27
- const deadRunners = Arr.empty<Runner>()
28
- const aliveRunners = MutableHashMap.empty<RunnerAddress, Runner>()
29
- yield* Effect.forEach(storedRunners, ([address, runner]) =>
30
- Effect.map(runnerHealth.isAlive(address), (isAlive) => {
31
- if (isAlive) {
32
- MutableHashMap.set(aliveRunners, address, runner)
33
- } else {
34
- deadRunners.push(runner)
35
- }
36
- }), { concurrency: "unbounded", discard: true })
37
- if (deadRunners.length > 0) {
38
- yield* Effect.logWarning("Ignoring runners that are no longer considered alive:", deadRunners)
39
- }
40
-
41
- // Determine which shards remain unassigned to a runner
42
- const assignedShards = MutableHashMap.empty<ShardId, RunnerAddress>()
43
- const invalidAssignments = Arr.empty<[ShardId, RunnerAddress]>()
44
- for (const [shard, address] of storedAssignments) {
45
- if (Option.isSome(address) && MutableHashMap.has(aliveRunners, address.value)) {
46
- MutableHashMap.set(assignedShards, shard, address.value)
47
- } else if (Option.isSome(address)) {
48
- invalidAssignments.push([shard, address.value])
49
- }
50
- }
51
- if (invalidAssignments.length > 0) {
52
- yield* Effect.logWarning(
53
- "Ignoring shard assignments for runners that are no longer considered alive: ",
54
- invalidAssignments
55
- )
56
- }
57
-
58
- // Construct the initial state
59
- const now = yield* Clock.currentTimeMillis
60
- const allRunners = MutableHashMap.empty<RunnerAddress, RunnerWithMetadata>()
61
- const runnerState = new Map<string, MutableHashMap.MutableHashMap<RunnerAddress, RunnerWithMetadata>>()
62
- for (const [address, runner] of aliveRunners) {
63
- const withMetadata = RunnerWithMetadata({ runner, registeredAt: now })
64
- MutableHashMap.set(allRunners, address, withMetadata)
65
- for (const group of runner.groups) {
66
- let groupMap = runnerState.get(group)
67
- if (!groupMap) {
68
- groupMap = MutableHashMap.empty<RunnerAddress, RunnerWithMetadata>()
69
- runnerState.set(group, groupMap)
70
- }
71
- MutableHashMap.set(groupMap, address, withMetadata)
72
- }
73
- }
74
-
75
- const shardState = new Map<string, Map<number, Option.Option<RunnerAddress>>>()
76
- for (const group of runnerState.keys()) {
77
- const groupMap = new Map<number, Option.Option<RunnerAddress>>()
78
- shardState.set(group, groupMap)
79
- for (let n = 1; n <= shardsPerGroup; n++) {
80
- const shardId = new ShardId({ group, id: n })
81
- groupMap.set(n, MutableHashMap.get(assignedShards, shardId))
82
- }
83
- }
84
-
85
- return new State(allRunners, runnerState, shardState, shardsPerGroup)
86
- })
87
-
88
- constructor(
89
- readonly allRunners: MutableHashMap.MutableHashMap<RunnerAddress, RunnerWithMetadata>,
90
- readonly runners: Map<string, MutableHashMap.MutableHashMap<RunnerAddress, RunnerWithMetadata>>,
91
- readonly shards: Map<string, Map<number, Option.Option<RunnerAddress>>>,
92
- readonly shardsPerGroup: number
93
- ) {
94
- this.assignments = MutableHashMap.empty<ShardId, Option.Option<RunnerAddress>>()
95
- this.perRunner = new Map<string, MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>>()
96
-
97
- for (const [address, meta] of this.allRunners) {
98
- for (const group of meta.runner.groups) {
99
- let runnerMap = this.perRunner.get(group)
100
- if (!runnerMap) {
101
- runnerMap = MutableHashMap.empty<RunnerAddress, Set<number>>()
102
- this.perRunner.set(group, runnerMap)
103
- }
104
- MutableHashMap.set(runnerMap, address, new Set())
105
- }
106
- }
107
-
108
- for (const [group, groupMap] of this.shards) {
109
- const perRunnerMap = this.perRunner.get(group)!
110
- for (const [id, address_] of groupMap) {
111
- const address = Option.filter(address_, (addr) => MutableHashMap.has(this.allRunners, addr))
112
- MutableHashMap.set(this.assignments, new ShardId({ group, id }), address)
113
- if (Option.isSome(address)) {
114
- Option.getOrUndefined(MutableHashMap.get(perRunnerMap, address.value))?.add(id)
115
- }
116
- }
117
- }
118
- }
119
-
120
- readonly assignments: MutableHashMap.MutableHashMap<ShardId, Option.Option<RunnerAddress>>
121
- readonly perRunner: Map<string, MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>>
122
-
123
- addGroup(group: string): void {
124
- this.runners.set(group, MutableHashMap.empty<RunnerAddress, RunnerWithMetadata>())
125
- const shardMap = new Map<number, Option.Option<RunnerAddress>>()
126
- this.shards.set(group, shardMap)
127
- for (let n = 1; n <= this.shardsPerGroup; n++) {
128
- shardMap.set(n, Option.none())
129
- MutableHashMap.set(this.assignments, new ShardId({ group, id: n }), Option.none())
130
- }
131
-
132
- const perRunnerMap = MutableHashMap.empty<RunnerAddress, Set<number>>()
133
- this.perRunner.set(group, perRunnerMap)
134
- for (const [address] of this.allRunners) {
135
- MutableHashMap.set(perRunnerMap, address, new Set())
136
- }
137
- }
138
-
139
- addAssignments(
140
- shards: Iterable<ShardId>,
141
- address: Option.Option<RunnerAddress>
142
- ) {
143
- for (const shardId of shards) {
144
- const currentAddress = Option.flatten(MutableHashMap.get(this.assignments, shardId))
145
- MutableHashMap.set(this.assignments, shardId, address)
146
- this.shards.get(shardId.group)?.set(shardId.id, address)
147
-
148
- const perRunner = this.perRunner.get(shardId.group)!
149
- if (Option.isSome(currentAddress)) {
150
- Option.getOrUndefined(MutableHashMap.get(perRunner, currentAddress.value))?.delete(shardId.id)
151
- }
152
- if (Option.isSome(address)) {
153
- Option.getOrUndefined(MutableHashMap.get(perRunner, address.value))?.add(shardId.id)
154
- }
155
- }
156
- }
157
-
158
- addRunner(runner: Runner, registeredAt: number): void {
159
- const withMetadata = RunnerWithMetadata({ runner, registeredAt })
160
- MutableHashMap.set(this.allRunners, runner.address, withMetadata)
161
- for (const group of runner.groups) {
162
- if (!this.runners.has(group)) {
163
- this.addGroup(group)
164
- }
165
- const groupMap = this.runners.get(group)!
166
- MutableHashMap.set(groupMap, runner.address, withMetadata)
167
- const perRunner = this.perRunner.get(group)!
168
- MutableHashMap.set(perRunner, runner.address, new Set())
169
- }
170
- }
171
-
172
- removeRunner(address: RunnerAddress): void {
173
- MutableHashMap.remove(this.allRunners, address)
174
- for (const group of this.runners.keys()) {
175
- const groupMap = this.runners.get(group)!
176
- MutableHashMap.remove(groupMap, address)
177
-
178
- const perRunner = this.perRunner.get(group)!
179
- MutableHashMap.remove(perRunner, address)
180
- }
181
- }
182
-
183
- get maxVersion(): Option.Option<number> {
184
- if (MutableHashMap.size(this.allRunners) === 0) return Option.none()
185
- let version: number | undefined = undefined
186
- for (const [, meta] of this.allRunners) {
187
- if (version === undefined || meta.runner.version > version) {
188
- version = meta.runner.version
189
- }
190
- }
191
- return Option.some(version!)
192
- }
193
-
194
- allRunnersHaveVersion(version: Option.Option<number>): boolean {
195
- return version.pipe(
196
- Option.map((max) => Arr.every(this.runnerVersions, (version) => version === max)),
197
- Option.getOrElse(constFalse)
198
- )
199
- }
200
-
201
- get shardStats(): {
202
- readonly perRunner: Map<string, number>
203
- readonly unassigned: number
204
- } {
205
- const perRunner = new Map<string, number>()
206
- let unassigned = 0
207
- for (const [, address] of this.assignments) {
208
- if (Option.isNone(address)) {
209
- unassigned++
210
- continue
211
- }
212
- const runner = address.value.toString()
213
- const count = perRunner.get(runner) ?? 0
214
- perRunner.set(runner, count + 1)
215
- }
216
-
217
- return { perRunner, unassigned }
218
- }
219
-
220
- shardsPerRunner(group: string): MutableHashMap.MutableHashMap<RunnerAddress, Set<number>> {
221
- const shards = MutableHashMap.empty<RunnerAddress, Set<number>>()
222
- const perRunner = this.perRunner.get(group)
223
- if (!perRunner || MutableHashMap.isEmpty(perRunner)) return shards
224
-
225
- for (const [address, shardSet] of perRunner) {
226
- MutableHashMap.set(shards, address, new Set(shardSet))
227
- }
228
-
229
- return shards
230
- }
231
-
232
- averageShardsPerRunner(group: string): number {
233
- const runnerCount = MutableHashMap.size(this.runners.get(group) ?? MutableHashMap.empty())
234
- const shardGroup = this.shards.get(group) ?? new Map()
235
- return runnerCount > 0 ? shardGroup.size / runnerCount : 0
236
- }
237
-
238
- get allUnassignedShards(): Array<ShardId> {
239
- const unassigned: Array<ShardId> = []
240
- for (const [shardId, address] of this.assignments) {
241
- if (Option.isNone(address)) {
242
- unassigned.push(shardId)
243
- }
244
- }
245
- return unassigned
246
- }
247
-
248
- unassignedShards(group: string): Array<number> {
249
- const shardIds: Array<number> = []
250
- const assignments = this.shards.get(group)!
251
- for (const [shard, address] of assignments) {
252
- if (Option.isNone(address)) {
253
- shardIds.push(shard)
254
- }
255
- }
256
- return shardIds
257
- }
258
-
259
- private get runnerVersions(): Array<number> {
260
- const runnerVersions: Array<number> = []
261
- for (const [, meta] of this.allRunners) {
262
- runnerVersions.push(meta.runner.version)
263
- }
264
- return runnerVersions
265
- }
266
- }
267
-
268
- /** @internal */
269
- export interface RunnerWithMetadata {
270
- readonly runner: Runner
271
- readonly registeredAt: number
272
- }
273
- /** @internal */
274
- export const RunnerWithMetadata = (runner: RunnerWithMetadata): RunnerWithMetadata => runner
275
-
276
- /** @internal */
277
- export function decideAssignmentsForShards(state: State, group: string): readonly [
278
- assignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
279
- unassignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
280
- changes: MutableHashSet.MutableHashSet<RunnerAddress>
281
- ] {
282
- const shardsPerRunner = state.shardsPerRunner(group)
283
- const maxVersion = state.maxVersion
284
- const shardsToRebalance = state.unassignedShards(group)
285
-
286
- if (state.allRunnersHaveVersion(maxVersion)) {
287
- const averageShardsPerRunner = state.averageShardsPerRunner(group)
288
- MutableHashMap.forEach(shardsPerRunner, (shards) => {
289
- const extraShards = Math.max(0, shards.size - averageShardsPerRunner)
290
- const iter = shards.values()
291
- for (let i = 0; i < extraShards; i++) {
292
- const shard = iter.next()
293
- if (shard.done) break
294
- shardsToRebalance.push(shard.value)
295
- }
296
- })
297
- }
298
-
299
- return pickNewRunners(shardsToRebalance, state, group, shardsPerRunner, maxVersion)
300
- }
301
-
302
- function pickNewRunners(
303
- shardsToRebalance: ReadonlyArray<number>,
304
- state: State,
305
- group: string,
306
- shardsPerRunner: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
307
- maybeMaxVersion = state.maxVersion
308
- ): readonly [
309
- assignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
310
- unassignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
311
- changes: MutableHashSet.MutableHashSet<RunnerAddress>
312
- ] {
313
- const addressAssignments = MutableHashMap.empty<RunnerAddress, Set<number>>()
314
- const unassignments = MutableHashMap.empty<RunnerAddress, Set<number>>()
315
- const changes = MutableHashSet.empty<RunnerAddress>()
316
-
317
- if (Option.isNone(maybeMaxVersion)) {
318
- return [addressAssignments, unassignments, changes]
319
- }
320
- const maxVersion = maybeMaxVersion.value
321
-
322
- const runnerGroup = state.runners.get(group)!
323
- const shardsGroup = state.shards.get(group)!
324
-
325
- for (const shardId of shardsToRebalance) {
326
- // Find the runner with the fewest assigned shards
327
- let candidate: RunnerAddress | undefined
328
- let candidateShards: Set<number> | undefined
329
-
330
- for (const [address, shards] of shardsPerRunner) {
331
- // Keep only runners with the maximum version
332
- const maybeRunnerMeta = MutableHashMap.get(runnerGroup, address)
333
- if (Option.isNone(maybeRunnerMeta)) continue
334
- const runnerMeta = maybeRunnerMeta.value
335
- if (runnerMeta.runner.version !== maxVersion) continue
336
-
337
- // Do not assign to a runner that has unassignments in the same rebalance
338
- if (MutableHashMap.has(unassignments, address)) continue
339
-
340
- if (candidate === undefined || shards.size < candidateShards!.size) {
341
- candidate = address
342
- candidateShards = shards
343
- }
344
- }
345
- if (!candidate || !candidateShards) break
346
-
347
- // If the old runner is the same as the new runner, do nothing
348
- const oldRunner = Option.getOrUndefined(shardsGroup.get(shardId) ?? Option.none())
349
- if (oldRunner && oldRunner.toString() === candidate.toString()) {
350
- continue
351
- }
352
- const oldShards = oldRunner && Option.getOrUndefined(MutableHashMap.get(shardsPerRunner, oldRunner))
353
-
354
- // If the new runner has one less, as many, or more shards than the
355
- // old runner, do not change anything
356
- if (oldShards && candidateShards.size + 1 >= oldShards.size) continue
357
-
358
- // Otherwise create a new assignment
359
- MutableHashMap.modifyAt(
360
- addressAssignments,
361
- candidate,
362
- Option.match({
363
- onNone: () => Option.some(new Set([shardId])),
364
- onSome: (shards) => {
365
- shards.add(shardId)
366
- return Option.some(shards)
367
- }
368
- })
369
- )
370
- if (oldRunner) {
371
- MutableHashMap.modifyAt(
372
- unassignments,
373
- oldRunner,
374
- Option.match({
375
- onNone: () => Option.some(new Set([shardId])),
376
- onSome: (shards) => {
377
- shards.add(shardId)
378
- return Option.some(shards)
379
- }
380
- })
381
- )
382
- }
383
-
384
- // Move the shard to the new runner
385
- candidateShards.add(shardId)
386
- if (oldShards) {
387
- oldShards.delete(shardId)
388
- }
389
-
390
- // Track changes
391
- MutableHashSet.add(changes, candidate)
392
- if (oldRunner) MutableHashSet.add(changes, oldRunner)
393
- }
394
-
395
- return [addressAssignments, unassignments, changes]
396
- }
397
-
398
- /** @internal */
399
- export const addAllNested = <K, V>(
400
- self: MutableHashMap.MutableHashMap<K, MutableHashSet.MutableHashSet<V>>,
401
- key: K,
402
- values: Iterable<V>
403
- ) => {
404
- const oset = MutableHashMap.get(self, key)
405
- if (Option.isSome(oset)) {
406
- for (const value of values) {
407
- MutableHashSet.add(oset.value, value)
408
- }
409
- } else {
410
- MutableHashMap.set(self, key, MutableHashSet.fromIterable(values))
411
- }
412
- }