@effect/cluster 0.37.2 → 0.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ClusterCron/package.json +6 -0
- package/dist/cjs/ClusterCron.js +86 -0
- package/dist/cjs/ClusterCron.js.map +1 -0
- package/dist/cjs/ClusterSchema.js +9 -1
- package/dist/cjs/ClusterSchema.js.map +1 -1
- package/dist/cjs/ClusterWorkflowEngine.js +21 -6
- package/dist/cjs/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cjs/Entity.js +6 -1
- package/dist/cjs/Entity.js.map +1 -1
- package/dist/cjs/EntityAddress.js +8 -1
- package/dist/cjs/EntityAddress.js.map +1 -1
- package/dist/cjs/MessageStorage.js +6 -4
- package/dist/cjs/MessageStorage.js.map +1 -1
- package/dist/cjs/Runner.js +15 -0
- package/dist/cjs/Runner.js.map +1 -1
- package/dist/cjs/RunnerAddress.js +8 -1
- package/dist/cjs/RunnerAddress.js.map +1 -1
- package/dist/cjs/Runners.js +5 -0
- package/dist/cjs/Runners.js.map +1 -1
- package/dist/cjs/ShardId.js +75 -7
- package/dist/cjs/ShardId.js.map +1 -1
- package/dist/cjs/ShardManager.js +63 -43
- package/dist/cjs/ShardManager.js.map +1 -1
- package/dist/cjs/ShardStorage.js +48 -35
- package/dist/cjs/ShardStorage.js.map +1 -1
- package/dist/cjs/Sharding.js +45 -37
- package/dist/cjs/Sharding.js.map +1 -1
- package/dist/cjs/ShardingConfig.js +9 -2
- package/dist/cjs/ShardingConfig.js.map +1 -1
- package/dist/cjs/Singleton.js +2 -2
- package/dist/cjs/Singleton.js.map +1 -1
- package/dist/cjs/SingletonAddress.js +2 -2
- package/dist/cjs/SingletonAddress.js.map +1 -1
- package/dist/cjs/SqlMessageStorage.js +32 -27
- package/dist/cjs/SqlMessageStorage.js.map +1 -1
- package/dist/cjs/SqlShardStorage.js +14 -14
- package/dist/cjs/SqlShardStorage.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/internal/entityManager.js +2 -1
- package/dist/cjs/internal/entityManager.js.map +1 -1
- package/dist/cjs/internal/shardManager.js +138 -37
- package/dist/cjs/internal/shardManager.js.map +1 -1
- package/dist/dts/ClusterCron.d.ts +37 -0
- package/dist/dts/ClusterCron.d.ts.map +1 -0
- package/dist/dts/ClusterSchema.d.ts +8 -0
- package/dist/dts/ClusterSchema.d.ts.map +1 -1
- package/dist/dts/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/dts/Entity.d.ts +10 -0
- package/dist/dts/Entity.d.ts.map +1 -1
- package/dist/dts/EntityAddress.d.ts +9 -3
- package/dist/dts/EntityAddress.d.ts.map +1 -1
- package/dist/dts/MessageStorage.d.ts +3 -3
- package/dist/dts/MessageStorage.d.ts.map +1 -1
- package/dist/dts/Runner.d.ts +15 -0
- package/dist/dts/Runner.d.ts.map +1 -1
- package/dist/dts/RunnerAddress.d.ts +5 -0
- package/dist/dts/RunnerAddress.d.ts.map +1 -1
- package/dist/dts/Runners.d.ts.map +1 -1
- package/dist/dts/ShardId.d.ts +60 -6
- package/dist/dts/ShardId.d.ts.map +1 -1
- package/dist/dts/ShardManager.d.ts +13 -13
- package/dist/dts/ShardManager.d.ts.map +1 -1
- package/dist/dts/ShardStorage.d.ts +11 -14
- package/dist/dts/ShardStorage.d.ts.map +1 -1
- package/dist/dts/Sharding.d.ts +4 -2
- package/dist/dts/Sharding.d.ts.map +1 -1
- package/dist/dts/ShardingConfig.d.ts +32 -6
- package/dist/dts/ShardingConfig.d.ts.map +1 -1
- package/dist/dts/Singleton.d.ts +3 -1
- package/dist/dts/Singleton.d.ts.map +1 -1
- package/dist/dts/SingletonAddress.d.ts +4 -3
- package/dist/dts/SingletonAddress.d.ts.map +1 -1
- package/dist/dts/SqlMessageStorage.d.ts +3 -2
- package/dist/dts/SqlMessageStorage.d.ts.map +1 -1
- package/dist/dts/SqlShardStorage.d.ts +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/ClusterCron.js +77 -0
- package/dist/esm/ClusterCron.js.map +1 -0
- package/dist/esm/ClusterSchema.js +7 -0
- package/dist/esm/ClusterSchema.js.map +1 -1
- package/dist/esm/ClusterWorkflowEngine.js +21 -6
- package/dist/esm/ClusterWorkflowEngine.js.map +1 -1
- package/dist/esm/Entity.js +6 -1
- package/dist/esm/Entity.js.map +1 -1
- package/dist/esm/EntityAddress.js +8 -1
- package/dist/esm/EntityAddress.js.map +1 -1
- package/dist/esm/MessageStorage.js +6 -4
- package/dist/esm/MessageStorage.js.map +1 -1
- package/dist/esm/Runner.js +15 -0
- package/dist/esm/Runner.js.map +1 -1
- package/dist/esm/RunnerAddress.js +8 -1
- package/dist/esm/RunnerAddress.js.map +1 -1
- package/dist/esm/Runners.js +5 -0
- package/dist/esm/Runners.js.map +1 -1
- package/dist/esm/ShardId.js +73 -6
- package/dist/esm/ShardId.js.map +1 -1
- package/dist/esm/ShardManager.js +64 -45
- package/dist/esm/ShardManager.js.map +1 -1
- package/dist/esm/ShardStorage.js +47 -35
- package/dist/esm/ShardStorage.js.map +1 -1
- package/dist/esm/Sharding.js +45 -37
- package/dist/esm/Sharding.js.map +1 -1
- package/dist/esm/ShardingConfig.js +9 -2
- package/dist/esm/ShardingConfig.js.map +1 -1
- package/dist/esm/Singleton.js +2 -2
- package/dist/esm/Singleton.js.map +1 -1
- package/dist/esm/SingletonAddress.js +2 -2
- package/dist/esm/SingletonAddress.js.map +1 -1
- package/dist/esm/SqlMessageStorage.js +32 -27
- package/dist/esm/SqlMessageStorage.js.map +1 -1
- package/dist/esm/SqlShardStorage.js +14 -14
- package/dist/esm/SqlShardStorage.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/entityManager.js +2 -1
- package/dist/esm/internal/entityManager.js.map +1 -1
- package/dist/esm/internal/shardManager.js +136 -36
- package/dist/esm/internal/shardManager.js.map +1 -1
- package/package.json +12 -4
- package/src/ClusterCron.ts +129 -0
- package/src/ClusterSchema.ts +9 -0
- package/src/ClusterWorkflowEngine.ts +37 -6
- package/src/Entity.ts +20 -1
- package/src/EntityAddress.ts +11 -1
- package/src/MessageStorage.ts +12 -7
- package/src/Runner.ts +18 -0
- package/src/RunnerAddress.ts +9 -1
- package/src/Runners.ts +5 -0
- package/src/ShardId.ts +81 -11
- package/src/ShardManager.ts +74 -45
- package/src/ShardStorage.ts +57 -49
- package/src/Sharding.ts +45 -39
- package/src/ShardingConfig.ts +36 -7
- package/src/Singleton.ts +5 -2
- package/src/SingletonAddress.ts +2 -2
- package/src/SqlMessageStorage.ts +36 -30
- package/src/SqlShardStorage.ts +15 -15
- package/src/index.ts +5 -0
- package/src/internal/entityManager.ts +2 -1
- package/src/internal/shardManager.ts +158 -52
package/src/SqlShardStorage.ts
CHANGED
@@ -66,21 +66,21 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
66
66
|
sql`
|
67
67
|
IF OBJECT_ID(N'${shardsTableSql}', N'U') IS NULL
|
68
68
|
CREATE TABLE ${shardsTableSql} (
|
69
|
-
shard_id
|
69
|
+
shard_id VARCHAR(50) PRIMARY KEY,
|
70
70
|
address VARCHAR(255)
|
71
71
|
)
|
72
72
|
`,
|
73
73
|
mysql: () =>
|
74
74
|
sql`
|
75
75
|
CREATE TABLE IF NOT EXISTS ${shardsTableSql} (
|
76
|
-
shard_id
|
76
|
+
shard_id VARCHAR(50) PRIMARY KEY,
|
77
77
|
address VARCHAR(255)
|
78
78
|
)
|
79
79
|
`,
|
80
80
|
pg: () =>
|
81
81
|
sql`
|
82
82
|
CREATE TABLE IF NOT EXISTS ${shardsTableSql} (
|
83
|
-
shard_id
|
83
|
+
shard_id VARCHAR(50) PRIMARY KEY,
|
84
84
|
address VARCHAR(255)
|
85
85
|
)
|
86
86
|
`,
|
@@ -88,7 +88,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
88
88
|
// sqlite
|
89
89
|
sql`
|
90
90
|
CREATE TABLE IF NOT EXISTS ${shardsTableSql} (
|
91
|
-
shard_id
|
91
|
+
shard_id TEXT PRIMARY KEY,
|
92
92
|
address TEXT
|
93
93
|
)
|
94
94
|
`
|
@@ -102,7 +102,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
102
102
|
sql`
|
103
103
|
IF OBJECT_ID(N'${locksTableSql}', N'U') IS NULL
|
104
104
|
CREATE TABLE ${locksTableSql} (
|
105
|
-
shard_id
|
105
|
+
shard_id VARCHAR(50) PRIMARY KEY,
|
106
106
|
address VARCHAR(255) NOT NULL,
|
107
107
|
acquired_at DATETIME NOT NULL
|
108
108
|
)
|
@@ -110,7 +110,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
110
110
|
mysql: () =>
|
111
111
|
sql`
|
112
112
|
CREATE TABLE IF NOT EXISTS ${locksTableSql} (
|
113
|
-
shard_id
|
113
|
+
shard_id VARCHAR(50) PRIMARY KEY,
|
114
114
|
address VARCHAR(255) NOT NULL,
|
115
115
|
acquired_at DATETIME NOT NULL
|
116
116
|
)
|
@@ -118,7 +118,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
118
118
|
pg: () =>
|
119
119
|
sql`
|
120
120
|
CREATE TABLE IF NOT EXISTS ${locksTableSql} (
|
121
|
-
shard_id
|
121
|
+
shard_id VARCHAR(50) PRIMARY KEY,
|
122
122
|
address VARCHAR(255) NOT NULL,
|
123
123
|
acquired_at TIMESTAMP NOT NULL
|
124
124
|
)
|
@@ -127,7 +127,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
127
127
|
// sqlite
|
128
128
|
sql`
|
129
129
|
CREATE TABLE IF NOT EXISTS ${locksTableSql} (
|
130
|
-
shard_id
|
130
|
+
shard_id TEXT PRIMARY KEY,
|
131
131
|
address TEXT NOT NULL,
|
132
132
|
acquired_at DATETIME NOT NULL
|
133
133
|
)
|
@@ -143,7 +143,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
143
143
|
const sqlNow = sql.literal(sqlNowString)
|
144
144
|
|
145
145
|
const lockExpiresAt = sql.onDialectOrElse({
|
146
|
-
pg: () => sql`${sqlNow} - INTERVAL '
|
146
|
+
pg: () => sql`${sqlNow} - INTERVAL '10 seconds'`,
|
147
147
|
mysql: () => sql`DATE_SUB(${sqlNow}, INTERVAL 5 SECOND)`,
|
148
148
|
mssql: () => sql`DATEADD(SECOND, -5, ${sqlNow})`,
|
149
149
|
orElse: () => sql`datetime(${sqlNow}, '-5 seconds')`
|
@@ -199,7 +199,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
199
199
|
orElse: () => sql.literal("FOR UPDATE")
|
200
200
|
})
|
201
201
|
|
202
|
-
return
|
202
|
+
return ShardStorage.makeEncoded({
|
203
203
|
getAssignments: sql`SELECT shard_id, address FROM ${shardsTableSql} ORDER BY shard_id`.values.pipe(
|
204
204
|
PersistenceError.refail,
|
205
205
|
withTracerDisabled
|
@@ -244,12 +244,12 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
244
244
|
function*(address, shardIds) {
|
245
245
|
const values = shardIds.map((shardId) => sql`(${shardId}, ${address}, ${sqlNow})`)
|
246
246
|
yield* acquireLock(address, values)
|
247
|
-
const currentLocks = yield* sql<{ shard_id:
|
247
|
+
const currentLocks = yield* sql<{ shard_id: string }>`
|
248
248
|
SELECT shard_id FROM ${sql(locksTable)}
|
249
|
-
WHERE address = ${address} AND ${
|
249
|
+
WHERE address = ${address} AND acquired_at >= ${lockExpiresAt}
|
250
250
|
${forUpdate}
|
251
|
-
|
252
|
-
return currentLocks.map((row) => row
|
251
|
+
`.values
|
252
|
+
return currentLocks.map((row) => row[0] as string)
|
253
253
|
},
|
254
254
|
sql.withTransaction,
|
255
255
|
PersistenceError.refail,
|
@@ -264,7 +264,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
264
264
|
sql`SELECT shard_id FROM ${locksTableSql} WHERE address = ${address} AND acquired_at >= ${lockExpiresAt} ${forUpdate}`
|
265
265
|
.values
|
266
266
|
),
|
267
|
-
Effect.map((rows) => rows.map((row) =>
|
267
|
+
Effect.map((rows) => rows.map((row) => row[0] as string)),
|
268
268
|
PersistenceError.refail,
|
269
269
|
withTracerDisabled
|
270
270
|
),
|
package/src/index.ts
CHANGED
@@ -7,6 +7,7 @@ import * as Context from "effect/Context"
|
|
7
7
|
import * as Duration from "effect/Duration"
|
8
8
|
import type { DurationInput } from "effect/Duration"
|
9
9
|
import * as Effect from "effect/Effect"
|
10
|
+
import * as Equal from "effect/Equal"
|
10
11
|
import * as Exit from "effect/Exit"
|
11
12
|
import * as FiberRef from "effect/FiberRef"
|
12
13
|
import { identity } from "effect/Function"
|
@@ -400,7 +401,7 @@ export const make = Effect.fnUntraced(function*<
|
|
400
401
|
Effect.suspend(function loop(): Effect.Effect<void> {
|
401
402
|
const toInterrupt = new Set<EntityState>()
|
402
403
|
for (const state of activeServers.values()) {
|
403
|
-
if (shardId
|
404
|
+
if (shardId[Equal.symbol](state.address.shardId)) {
|
404
405
|
toInterrupt.add(state)
|
405
406
|
}
|
406
407
|
}
|
@@ -14,7 +14,9 @@ import { ShardStorage } from "../ShardStorage.js"
|
|
14
14
|
|
15
15
|
/** @internal */
|
16
16
|
export class State {
|
17
|
-
static fromStorage = Effect.fnUntraced(function*(
|
17
|
+
static fromStorage = Effect.fnUntraced(function*(
|
18
|
+
shardsPerGroup: number
|
19
|
+
) {
|
18
20
|
const storage = yield* ShardStorage
|
19
21
|
const runnerHealth = yield* RunnerHealth
|
20
22
|
|
@@ -38,11 +40,11 @@ export class State {
|
|
38
40
|
}
|
39
41
|
|
40
42
|
// Determine which shards remain unassigned to a runner
|
41
|
-
const assignedShards =
|
43
|
+
const assignedShards = MutableHashMap.empty<ShardId, RunnerAddress>()
|
42
44
|
const invalidAssignments = Arr.empty<[ShardId, RunnerAddress]>()
|
43
45
|
for (const [shard, address] of storedAssignments) {
|
44
46
|
if (Option.isSome(address) && MutableHashMap.has(aliveRunners, address.value)) {
|
45
|
-
|
47
|
+
MutableHashMap.set(assignedShards, shard, address.value)
|
46
48
|
} else if (Option.isSome(address)) {
|
47
49
|
invalidAssignments.push([shard, address.value])
|
48
50
|
}
|
@@ -56,29 +58,96 @@ export class State {
|
|
56
58
|
|
57
59
|
// Construct the initial state
|
58
60
|
const now = yield* Clock.currentTimeMillis
|
59
|
-
const
|
61
|
+
const allRunners = MutableHashMap.empty<RunnerAddress, RunnerWithMetadata>()
|
62
|
+
const runnerState = new Map<string, MutableHashMap.MutableHashMap<RunnerAddress, RunnerWithMetadata>>()
|
63
|
+
// for (const group of groups) {
|
64
|
+
// runnerState.set(group, MutableHashMap.empty<RunnerAddress, RunnerWithMetadata>())
|
65
|
+
// }
|
60
66
|
for (const [address, runner] of aliveRunners) {
|
61
|
-
|
67
|
+
const withMetadata = RunnerWithMetadata({ runner, registeredAt: now })
|
68
|
+
MutableHashMap.set(allRunners, address, withMetadata)
|
69
|
+
for (const group of runner.groups) {
|
70
|
+
let groupMap = runnerState.get(group)
|
71
|
+
if (!groupMap) {
|
72
|
+
groupMap = MutableHashMap.empty<RunnerAddress, RunnerWithMetadata>()
|
73
|
+
runnerState.set(group, groupMap)
|
74
|
+
}
|
75
|
+
MutableHashMap.set(groupMap, address, withMetadata)
|
76
|
+
}
|
62
77
|
}
|
63
78
|
|
64
|
-
const shardState = new Map<
|
65
|
-
for (
|
66
|
-
const
|
67
|
-
shardState.set(
|
79
|
+
const shardState = new Map<string, Map<number, Option.Option<RunnerAddress>>>()
|
80
|
+
for (const group of runnerState.keys()) {
|
81
|
+
const groupMap = new Map<number, Option.Option<RunnerAddress>>()
|
82
|
+
shardState.set(group, groupMap)
|
83
|
+
for (let n = 1; n <= shardsPerGroup; n++) {
|
84
|
+
const shardId = new ShardId({ group, id: n })
|
85
|
+
groupMap.set(n, MutableHashMap.get(assignedShards, shardId))
|
86
|
+
}
|
68
87
|
}
|
69
88
|
|
70
|
-
return new State(runnerState, shardState)
|
89
|
+
return new State(allRunners, runnerState, shardState, shardsPerGroup)
|
71
90
|
})
|
72
91
|
|
73
92
|
constructor(
|
74
|
-
readonly
|
75
|
-
readonly
|
76
|
-
|
93
|
+
readonly allRunners: MutableHashMap.MutableHashMap<RunnerAddress, RunnerWithMetadata>,
|
94
|
+
readonly runners: Map<string, MutableHashMap.MutableHashMap<RunnerAddress, RunnerWithMetadata>>,
|
95
|
+
readonly shards: Map<string, Map<number, Option.Option<RunnerAddress>>>,
|
96
|
+
readonly shardsPerGroup: number
|
97
|
+
) {
|
98
|
+
this.assignments = MutableHashMap.empty<ShardId, Option.Option<RunnerAddress>>()
|
99
|
+
for (const [group, groupMap] of this.shards) {
|
100
|
+
for (const [id, address] of groupMap) {
|
101
|
+
MutableHashMap.set(this.assignments, new ShardId({ group, id }), address)
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
readonly assignments: MutableHashMap.MutableHashMap<ShardId, Option.Option<RunnerAddress>>
|
107
|
+
|
108
|
+
addGroup(group: string): void {
|
109
|
+
this.runners.set(group, MutableHashMap.empty<RunnerAddress, RunnerWithMetadata>())
|
110
|
+
const shardMap = new Map<number, Option.Option<RunnerAddress>>()
|
111
|
+
for (let n = 1; n <= this.shardsPerGroup; n++) {
|
112
|
+
shardMap.set(n, Option.none())
|
113
|
+
MutableHashMap.set(this.assignments, new ShardId({ group, id: n }), Option.none())
|
114
|
+
}
|
115
|
+
this.shards.set(group, shardMap)
|
116
|
+
}
|
117
|
+
|
118
|
+
addAssignments(
|
119
|
+
shards: Iterable<ShardId>,
|
120
|
+
address: Option.Option<RunnerAddress>
|
121
|
+
) {
|
122
|
+
for (const shardId of shards) {
|
123
|
+
MutableHashMap.set(this.assignments, shardId, address)
|
124
|
+
this.shards.get(shardId.group)?.set(shardId.id, address)
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
addRunner(runner: Runner, registeredAt: number): void {
|
129
|
+
const withMetadata = RunnerWithMetadata({ runner, registeredAt })
|
130
|
+
MutableHashMap.set(this.allRunners, runner.address, withMetadata)
|
131
|
+
for (const group of runner.groups) {
|
132
|
+
if (!this.runners.has(group)) {
|
133
|
+
this.addGroup(group)
|
134
|
+
}
|
135
|
+
const groupMap = this.runners.get(group)!
|
136
|
+
MutableHashMap.set(groupMap, runner.address, withMetadata)
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
removeRunner(address: RunnerAddress): void {
|
141
|
+
MutableHashMap.remove(this.allRunners, address)
|
142
|
+
for (const groupMap of this.runners.values()) {
|
143
|
+
MutableHashMap.remove(groupMap, address)
|
144
|
+
}
|
145
|
+
}
|
77
146
|
|
78
147
|
get maxVersion(): Option.Option<number> {
|
79
|
-
if (MutableHashMap.size(this.
|
148
|
+
if (MutableHashMap.size(this.allRunners) === 0) return Option.none()
|
80
149
|
let version: number | undefined = undefined
|
81
|
-
for (const [, meta] of this.
|
150
|
+
for (const [, meta] of this.allRunners) {
|
82
151
|
if (version === undefined || meta.runner.version > version) {
|
83
152
|
version = meta.runner.version
|
84
153
|
}
|
@@ -93,31 +162,45 @@ export class State {
|
|
93
162
|
)
|
94
163
|
}
|
95
164
|
|
96
|
-
|
97
|
-
const
|
165
|
+
shardsPerRunner(group: string): MutableHashMap.MutableHashMap<RunnerAddress, Set<number>> {
|
166
|
+
const groupRunners = this.runners.get(group)
|
167
|
+
const shards = MutableHashMap.empty<RunnerAddress, Set<number>>()
|
98
168
|
|
99
|
-
if (MutableHashMap.isEmpty(
|
100
|
-
MutableHashMap.forEach(
|
169
|
+
if (!groupRunners || MutableHashMap.isEmpty(groupRunners)) return shards
|
170
|
+
MutableHashMap.forEach(groupRunners, (_, address) => {
|
101
171
|
MutableHashMap.set(shards, address, new Set())
|
102
172
|
})
|
103
173
|
|
104
|
-
|
174
|
+
const assignments = this.shards.get(group)!
|
175
|
+
for (const [id, address] of assignments) {
|
105
176
|
if (Option.isNone(address)) continue
|
106
177
|
const shardIds = Option.getOrUndefined(MutableHashMap.get(shards, address.value))!
|
107
|
-
shardIds.add(
|
178
|
+
shardIds.add(id)
|
108
179
|
}
|
109
180
|
|
110
181
|
return shards
|
111
182
|
}
|
112
183
|
|
113
|
-
|
114
|
-
const runnerCount = MutableHashMap.size(this.runners)
|
115
|
-
|
184
|
+
averageShardsPerRunner(group: string): number {
|
185
|
+
const runnerCount = MutableHashMap.size(this.runners.get(group) ?? MutableHashMap.empty())
|
186
|
+
const shardGroup = this.shards.get(group) ?? new Map()
|
187
|
+
return runnerCount > 0 ? shardGroup.size / runnerCount : 0
|
188
|
+
}
|
189
|
+
|
190
|
+
get allUnassignedShards(): Array<ShardId> {
|
191
|
+
const unassigned: Array<ShardId> = []
|
192
|
+
for (const [shardId, address] of this.assignments) {
|
193
|
+
if (Option.isNone(address)) {
|
194
|
+
unassigned.push(shardId)
|
195
|
+
}
|
196
|
+
}
|
197
|
+
return unassigned
|
116
198
|
}
|
117
199
|
|
118
|
-
|
119
|
-
const shardIds: Array<
|
120
|
-
|
200
|
+
unassignedShards(group: string): Array<number> {
|
201
|
+
const shardIds: Array<number> = []
|
202
|
+
const assignments = this.shards.get(group)!
|
203
|
+
for (const [shard, address] of assignments) {
|
121
204
|
if (Option.isNone(address)) {
|
122
205
|
shardIds.push(shard)
|
123
206
|
}
|
@@ -127,7 +210,7 @@ export class State {
|
|
127
210
|
|
128
211
|
private get runnerVersions(): Array<number> {
|
129
212
|
const runnerVersions: Array<number> = []
|
130
|
-
for (const [, meta] of this.
|
213
|
+
for (const [, meta] of this.allRunners) {
|
131
214
|
runnerVersions.push(meta.runner.version)
|
132
215
|
}
|
133
216
|
return runnerVersions
|
@@ -143,36 +226,39 @@ export interface RunnerWithMetadata {
|
|
143
226
|
export const RunnerWithMetadata = (runner: RunnerWithMetadata): RunnerWithMetadata => runner
|
144
227
|
|
145
228
|
/** @internal */
|
146
|
-
export function decideAssignmentsForUnassignedShards(state: State): readonly [
|
147
|
-
assignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<
|
148
|
-
unassignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<
|
229
|
+
export function decideAssignmentsForUnassignedShards(state: State, group: string): readonly [
|
230
|
+
assignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
|
231
|
+
unassignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
|
149
232
|
changes: MutableHashSet.MutableHashSet<RunnerAddress>
|
150
233
|
] {
|
151
|
-
return pickNewRunners(state.unassignedShards, state, true, 1)
|
234
|
+
return pickNewRunners(state.unassignedShards(group), state, group, true, 1)
|
152
235
|
}
|
153
236
|
|
154
|
-
const allocationOrder: Order.Order<[
|
237
|
+
const allocationOrder: Order.Order<[number, number, number]> = Order.combine(
|
155
238
|
Order.mapInput(Order.number, ([, shards]) => shards),
|
156
239
|
Order.mapInput(Order.number, ([, , registeredAt]) => registeredAt)
|
157
240
|
)
|
158
241
|
|
159
242
|
/** @internal */
|
160
|
-
export function decideAssignmentsForUnbalancedShards(state: State, rate: number): readonly [
|
161
|
-
assignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<
|
162
|
-
unassignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<
|
243
|
+
export function decideAssignmentsForUnbalancedShards(state: State, group: string, rate: number): readonly [
|
244
|
+
assignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
|
245
|
+
unassignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
|
163
246
|
changes: MutableHashSet.MutableHashSet<RunnerAddress>
|
164
247
|
] {
|
165
|
-
const shardsPerRunner = state.shardsPerRunner
|
248
|
+
const shardsPerRunner = state.shardsPerRunner(group)
|
166
249
|
const maxVersion = state.maxVersion
|
167
|
-
const extraShardsToAllocate = Arr.empty<[
|
250
|
+
const extraShardsToAllocate = Arr.empty<[number, shardsInverse: number, registeredAt: number]>()
|
251
|
+
|
252
|
+
const runnerGroup = state.runners.get(group)!
|
253
|
+
const shardsGroup = state.shards.get(group)!
|
168
254
|
|
169
255
|
if (state.allRunnersHaveVersion(maxVersion)) {
|
170
|
-
const averageShardsPerRunner = state.averageShardsPerRunner
|
256
|
+
const averageShardsPerRunner = state.averageShardsPerRunner(group)
|
171
257
|
MutableHashMap.forEach(shardsPerRunner, (shards) => {
|
172
258
|
// Count how many extra shards there are compared to the average
|
173
259
|
const extraShards = Math.max(0, shards.size - averageShardsPerRunner)
|
174
260
|
for (const shard of takeRandom(shards, extraShards)) {
|
175
|
-
const maybeAddress =
|
261
|
+
const maybeAddress = shardsGroup.get(shard) ?? Option.none()
|
176
262
|
if (Option.isNone(maybeAddress)) {
|
177
263
|
extraShardsToAllocate.push([shard, Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER])
|
178
264
|
continue
|
@@ -184,7 +270,7 @@ export function decideAssignmentsForUnbalancedShards(state: State, rate: number)
|
|
184
270
|
onNone: () => Number.MIN_SAFE_INTEGER,
|
185
271
|
onSome: (shards) => -shards.size
|
186
272
|
}),
|
187
|
-
Option.match(MutableHashMap.get(
|
273
|
+
Option.match(MutableHashMap.get(runnerGroup, address), {
|
188
274
|
onNone: () => Number.MIN_SAFE_INTEGER,
|
189
275
|
onSome: (meta) => meta.registeredAt
|
190
276
|
})
|
@@ -195,23 +281,24 @@ export function decideAssignmentsForUnbalancedShards(state: State, rate: number)
|
|
195
281
|
|
196
282
|
const sortedShardsToRebalance = extraShardsToAllocate.sort(allocationOrder).map(([shard]) => shard)
|
197
283
|
|
198
|
-
return pickNewRunners(sortedShardsToRebalance, state, false, rate, shardsPerRunner, maxVersion)
|
284
|
+
return pickNewRunners(sortedShardsToRebalance, state, group, false, rate, shardsPerRunner, maxVersion)
|
199
285
|
}
|
200
286
|
|
201
287
|
function pickNewRunners(
|
202
|
-
shardsToRebalance: ReadonlyArray<
|
288
|
+
shardsToRebalance: ReadonlyArray<number>,
|
203
289
|
state: State,
|
290
|
+
group: string,
|
204
291
|
immediate: boolean,
|
205
292
|
rate: number,
|
206
|
-
shardsPerRunner = state.shardsPerRunner,
|
293
|
+
shardsPerRunner = state.shardsPerRunner(group),
|
207
294
|
maybeMaxVersion = state.maxVersion
|
208
295
|
): readonly [
|
209
|
-
assignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<
|
210
|
-
unassignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<
|
296
|
+
assignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
|
297
|
+
unassignments: MutableHashMap.MutableHashMap<RunnerAddress, Set<number>>,
|
211
298
|
changes: MutableHashSet.MutableHashSet<RunnerAddress>
|
212
299
|
] {
|
213
|
-
const addressAssignments = MutableHashMap.empty<RunnerAddress, Set<
|
214
|
-
const unassignments = MutableHashMap.empty<RunnerAddress, Set<
|
300
|
+
const addressAssignments = MutableHashMap.empty<RunnerAddress, Set<number>>()
|
301
|
+
const unassignments = MutableHashMap.empty<RunnerAddress, Set<number>>()
|
215
302
|
const changes = MutableHashSet.empty<RunnerAddress>()
|
216
303
|
|
217
304
|
if (Option.isNone(maybeMaxVersion)) {
|
@@ -219,14 +306,17 @@ function pickNewRunners(
|
|
219
306
|
}
|
220
307
|
const maxVersion = maybeMaxVersion.value
|
221
308
|
|
309
|
+
const runnerGroup = state.runners.get(group)!
|
310
|
+
const shardsGroup = state.shards.get(group)!
|
311
|
+
|
222
312
|
for (const shardId of shardsToRebalance) {
|
223
313
|
// Find the runner with the fewest assigned shards
|
224
314
|
let candidate: RunnerAddress | undefined
|
225
|
-
let candidateShards: Set<
|
315
|
+
let candidateShards: Set<number> | undefined
|
226
316
|
|
227
317
|
for (const [address, shards] of shardsPerRunner) {
|
228
318
|
// Keep only runners with the maximum version
|
229
|
-
const maybeRunnerMeta = MutableHashMap.get(
|
319
|
+
const maybeRunnerMeta = MutableHashMap.get(runnerGroup, address)
|
230
320
|
if (Option.isNone(maybeRunnerMeta)) continue
|
231
321
|
const runnerMeta = maybeRunnerMeta.value
|
232
322
|
if (runnerMeta.runner.version !== maxVersion) continue
|
@@ -238,7 +328,7 @@ function pickNewRunners(
|
|
238
328
|
// occur immediately
|
239
329
|
if (!immediate) {
|
240
330
|
const assignmentCount = Option.getOrUndefined(MutableHashMap.get(addressAssignments, address))?.size ?? 0
|
241
|
-
if (assignmentCount >=
|
331
|
+
if (assignmentCount >= shardsGroup.size * rate) continue
|
242
332
|
}
|
243
333
|
|
244
334
|
if (candidate === undefined || shards.size < candidateShards!.size) {
|
@@ -249,7 +339,7 @@ function pickNewRunners(
|
|
249
339
|
if (!candidate || !candidateShards) break
|
250
340
|
|
251
341
|
// If the old runner is the same as the new runner, do nothing
|
252
|
-
const oldRunner = Option.getOrUndefined(
|
342
|
+
const oldRunner = Option.getOrUndefined(shardsGroup.get(shardId) ?? Option.none())
|
253
343
|
if (oldRunner && oldRunner.toString() === candidate.toString()) {
|
254
344
|
continue
|
255
345
|
}
|
@@ -316,3 +406,19 @@ function swap<A>(array: Array<A>, i: number, j: number): ReadonlyArray<A> {
|
|
316
406
|
array[j] = tmp
|
317
407
|
return array
|
318
408
|
}
|
409
|
+
|
410
|
+
/** @internal */
|
411
|
+
export const addAllNested = <K, V>(
|
412
|
+
self: MutableHashMap.MutableHashMap<K, MutableHashSet.MutableHashSet<V>>,
|
413
|
+
key: K,
|
414
|
+
values: Iterable<V>
|
415
|
+
) => {
|
416
|
+
const oset = MutableHashMap.get(self, key)
|
417
|
+
if (Option.isSome(oset)) {
|
418
|
+
for (const value of values) {
|
419
|
+
MutableHashSet.add(oset.value, value)
|
420
|
+
}
|
421
|
+
} else {
|
422
|
+
MutableHashMap.set(self, key, MutableHashSet.fromIterable(values))
|
423
|
+
}
|
424
|
+
}
|