@effect/cluster 0.37.1 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +99 -78
- 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 +45 -36
- 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 +4 -4
- 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 +99 -78
- 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 +44 -36
- 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 +13 -5
- package/src/ClusterCron.ts +129 -0
- package/src/ClusterSchema.ts +9 -0
- package/src/ClusterWorkflowEngine.ts +93 -58
- 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 +51 -47
- 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/Sharding.ts
CHANGED
@@ -21,6 +21,7 @@ import * as HashMap from "effect/HashMap"
|
|
21
21
|
import * as Iterable from "effect/Iterable"
|
22
22
|
import * as Layer from "effect/Layer"
|
23
23
|
import * as MutableHashMap from "effect/MutableHashMap"
|
24
|
+
import * as MutableHashSet from "effect/MutableHashSet"
|
24
25
|
import * as MutableRef from "effect/MutableRef"
|
25
26
|
import * as Option from "effect/Option"
|
26
27
|
import * as Predicate from "effect/Predicate"
|
@@ -74,7 +75,7 @@ export class Sharding extends Context.Tag("@effect/cluster/Sharding")<Sharding,
|
|
74
75
|
* Returns the `ShardId` of the shard to which the entity at the specified
|
75
76
|
* `address` is assigned.
|
76
77
|
*/
|
77
|
-
readonly getShardId: (entityId: EntityId) => ShardId
|
78
|
+
readonly getShardId: (entityId: EntityId, group: string) => ShardId
|
78
79
|
|
79
80
|
/**
|
80
81
|
* Returns `true` if sharding is shutting down, `false` otherwise.
|
@@ -115,7 +116,10 @@ export class Sharding extends Context.Tag("@effect/cluster/Sharding")<Sharding,
|
|
115
116
|
*/
|
116
117
|
readonly registerSingleton: <E, R>(
|
117
118
|
name: string,
|
118
|
-
run: Effect.Effect<void, E, R
|
119
|
+
run: Effect.Effect<void, E, R>,
|
120
|
+
options?: {
|
121
|
+
readonly shardGroup?: string | undefined
|
122
|
+
}
|
119
123
|
) => Effect.Effect<void, never, Exclude<R, Scope.Scope>>
|
120
124
|
|
121
125
|
/**
|
@@ -182,10 +186,10 @@ const make = Effect.gen(function*() {
|
|
182
186
|
const entityManagers = new Map<EntityType, EntityManagerState>()
|
183
187
|
|
184
188
|
const shardAssignments = MutableHashMap.empty<ShardId, RunnerAddress>()
|
185
|
-
const selfShards =
|
189
|
+
const selfShards = MutableHashSet.empty<ShardId>()
|
186
190
|
|
187
191
|
// the active shards are the ones that we have acquired the lock for
|
188
|
-
const acquiredShards =
|
192
|
+
const acquiredShards = MutableHashSet.empty<ShardId>()
|
189
193
|
const activeShardsLatch = yield* Effect.makeLatch(false)
|
190
194
|
|
191
195
|
const events = yield* PubSub.unbounded<ShardingRegistrationEvent>()
|
@@ -194,12 +198,13 @@ const make = Effect.gen(function*() {
|
|
194
198
|
const isLocalRunner = (address: RunnerAddress) =>
|
195
199
|
Option.isSome(config.runnerAddress) && Equal.equals(address, config.runnerAddress.value)
|
196
200
|
|
197
|
-
function getShardId(entityId: EntityId): ShardId {
|
198
|
-
|
201
|
+
function getShardId(entityId: EntityId, group: string): ShardId {
|
202
|
+
const id = Math.abs(hashString(entityId) % config.shardsPerGroup) + 1
|
203
|
+
return ShardId.make({ group, id }, { disableValidation: true })
|
199
204
|
}
|
200
205
|
|
201
206
|
function isEntityOnLocalShards(address: EntityAddress): boolean {
|
202
|
-
return
|
207
|
+
return MutableHashSet.has(acquiredShards, address.shardId)
|
203
208
|
}
|
204
209
|
|
205
210
|
// --- Shard acquisition ---
|
@@ -211,30 +216,30 @@ const make = Effect.gen(function*() {
|
|
211
216
|
return Effect.ignore(shardStorage.releaseAll(selfAddress))
|
212
217
|
})
|
213
218
|
|
214
|
-
const releasingShards =
|
219
|
+
const releasingShards = MutableHashSet.empty<ShardId>()
|
215
220
|
yield* Effect.gen(function*() {
|
216
221
|
while (true) {
|
217
222
|
yield* activeShardsLatch.await
|
218
223
|
|
219
224
|
// if a shard is no longer assigned to this runner, we release it
|
220
225
|
for (const shardId of acquiredShards) {
|
221
|
-
if (
|
222
|
-
|
223
|
-
|
226
|
+
if (MutableHashSet.has(selfShards, shardId)) continue
|
227
|
+
MutableHashSet.remove(acquiredShards, shardId)
|
228
|
+
MutableHashSet.add(releasingShards, shardId)
|
224
229
|
}
|
225
230
|
// if a shard has been assigned to this runner, we acquire it
|
226
|
-
const unacquiredShards =
|
231
|
+
const unacquiredShards = MutableHashSet.empty<ShardId>()
|
227
232
|
for (const shardId of selfShards) {
|
228
|
-
if (
|
229
|
-
|
233
|
+
if (MutableHashSet.has(acquiredShards, shardId) || MutableHashSet.has(releasingShards, shardId)) continue
|
234
|
+
MutableHashSet.add(unacquiredShards, shardId)
|
230
235
|
}
|
231
236
|
|
232
|
-
if (
|
237
|
+
if (MutableHashSet.size(releasingShards) > 0) {
|
233
238
|
yield* Effect.forkIn(syncSingletons, shardingScope)
|
234
239
|
yield* releaseShards
|
235
240
|
}
|
236
241
|
|
237
|
-
if (
|
242
|
+
if (MutableHashSet.size(unacquiredShards) === 0) {
|
238
243
|
yield* activeShardsLatch.close
|
239
244
|
continue
|
240
245
|
}
|
@@ -242,7 +247,7 @@ const make = Effect.gen(function*() {
|
|
242
247
|
const acquired = yield* shardStorage.acquire(selfAddress, unacquiredShards)
|
243
248
|
yield* Effect.ignore(storage.resetShards(acquired))
|
244
249
|
for (const shardId of acquired) {
|
245
|
-
|
250
|
+
MutableHashSet.add(acquiredShards, shardId)
|
246
251
|
}
|
247
252
|
if (acquired.length > 0) {
|
248
253
|
yield* storageReadLatch.open
|
@@ -272,12 +277,12 @@ const make = Effect.gen(function*() {
|
|
272
277
|
).pipe(
|
273
278
|
Effect.flatMap((acquired) => {
|
274
279
|
for (const shardId of acquiredShards) {
|
275
|
-
if (!acquired.
|
276
|
-
|
277
|
-
|
280
|
+
if (!acquired.some((_) => _[Equal.symbol](shardId))) {
|
281
|
+
MutableHashSet.remove(acquiredShards, shardId)
|
282
|
+
MutableHashSet.add(releasingShards, shardId)
|
278
283
|
}
|
279
284
|
}
|
280
|
-
return
|
285
|
+
return MutableHashSet.size(releasingShards) > 0 ?
|
281
286
|
Effect.andThen(
|
282
287
|
Effect.forkIn(syncSingletons, shardingScope),
|
283
288
|
releaseShards
|
@@ -315,7 +320,7 @@ const make = Effect.gen(function*() {
|
|
315
320
|
runner: selfAddress
|
316
321
|
}),
|
317
322
|
Effect.andThen(() => {
|
318
|
-
|
323
|
+
MutableHashSet.remove(releasingShards, shardId)
|
319
324
|
})
|
320
325
|
),
|
321
326
|
{ concurrency: "unbounded", discard: true }
|
@@ -325,7 +330,7 @@ const make = Effect.gen(function*() {
|
|
325
330
|
}
|
326
331
|
|
327
332
|
const clearSelfShards = Effect.suspend(() => {
|
328
|
-
|
333
|
+
MutableHashSet.clear(selfShards)
|
329
334
|
return activeShardsLatch.open
|
330
335
|
})
|
331
336
|
|
@@ -336,9 +341,10 @@ const make = Effect.gen(function*() {
|
|
336
341
|
const withSingletonLock = Effect.unsafeMakeSemaphore(1).withPermits(1)
|
337
342
|
|
338
343
|
const registerSingleton: Sharding["Type"]["registerSingleton"] = Effect.fnUntraced(
|
339
|
-
function*(name, run) {
|
344
|
+
function*(name, run, options) {
|
345
|
+
const shardGroup = options?.shardGroup ?? "default"
|
340
346
|
const address = new SingletonAddress({
|
341
|
-
shardId: getShardId(EntityId.make(name)),
|
347
|
+
shardId: getShardId(EntityId.make(name), shardGroup),
|
342
348
|
name
|
343
349
|
})
|
344
350
|
|
@@ -365,7 +371,7 @@ const make = Effect.gen(function*() {
|
|
365
371
|
yield* PubSub.publish(events, SingletonRegistered({ address }))
|
366
372
|
|
367
373
|
// start if we are on the right shard
|
368
|
-
if (
|
374
|
+
if (MutableHashSet.has(acquiredShards, address.shardId)) {
|
369
375
|
yield* Effect.logDebug("Starting singleton", address)
|
370
376
|
yield* FiberMap.run(singletonFibers, address, wrappedRun)
|
371
377
|
}
|
@@ -377,7 +383,7 @@ const make = Effect.gen(function*() {
|
|
377
383
|
for (const [shardId, map] of singletons) {
|
378
384
|
for (const [address, run] of map) {
|
379
385
|
const running = FiberMap.unsafeHas(singletonFibers, address)
|
380
|
-
const shouldBeRunning =
|
386
|
+
const shouldBeRunning = MutableHashSet.has(acquiredShards, shardId)
|
381
387
|
if (running && !shouldBeRunning) {
|
382
388
|
yield* Effect.logDebug("Stopping singleton", address)
|
383
389
|
internalInterruptors.add(yield* Effect.fiberId)
|
@@ -452,7 +458,7 @@ const make = Effect.gen(function*() {
|
|
452
458
|
currentSentRequestIds.add(message.envelope.requestId)
|
453
459
|
}
|
454
460
|
const address = message.envelope.address
|
455
|
-
if (!
|
461
|
+
if (!MutableHashSet.has(acquiredShards, address.shardId)) {
|
456
462
|
return Effect.void
|
457
463
|
}
|
458
464
|
const state = entityManagers.get(address.entityType)
|
@@ -580,7 +586,7 @@ const make = Effect.gen(function*() {
|
|
580
586
|
|
581
587
|
while (!done) {
|
582
588
|
// if the shard is no longer assigned to this runner, we stop
|
583
|
-
if (!
|
589
|
+
if (!MutableHashSet.has(acquiredShards, address.shardId)) {
|
584
590
|
return
|
585
591
|
}
|
586
592
|
|
@@ -606,7 +612,7 @@ const make = Effect.gen(function*() {
|
|
606
612
|
EntityNotManagedByRunner | EntityNotAssignedToRunner
|
607
613
|
> = Effect.catchTags(
|
608
614
|
Effect.suspend(() => {
|
609
|
-
if (!
|
615
|
+
if (!MutableHashSet.has(acquiredShards, address.shardId)) {
|
610
616
|
return Effect.fail(new EntityNotAssignedToRunner({ address }))
|
611
617
|
}
|
612
618
|
|
@@ -805,7 +811,7 @@ const make = Effect.gen(function*() {
|
|
805
811
|
yield* Effect.gen(function*() {
|
806
812
|
yield* Effect.logDebug("Registering with shard manager")
|
807
813
|
if (Option.isSome(config.runnerAddress)) {
|
808
|
-
const machineId = yield* shardManager.register(config.runnerAddress.value)
|
814
|
+
const machineId = yield* shardManager.register(config.runnerAddress.value, config.shardGroups)
|
809
815
|
yield* snowflakeGen.setMachineId(machineId)
|
810
816
|
}
|
811
817
|
|
@@ -832,8 +838,8 @@ const make = Effect.gen(function*() {
|
|
832
838
|
}
|
833
839
|
if (!MutableRef.get(isShutdown) && isLocalRunner(event.address)) {
|
834
840
|
for (const shardId of event.shards) {
|
835
|
-
if (
|
836
|
-
|
841
|
+
if (MutableHashSet.has(selfShards, shardId)) continue
|
842
|
+
MutableHashSet.add(selfShards, shardId)
|
837
843
|
}
|
838
844
|
yield* activeShardsLatch.open
|
839
845
|
}
|
@@ -845,7 +851,7 @@ const make = Effect.gen(function*() {
|
|
845
851
|
}
|
846
852
|
if (isLocalRunner(event.address)) {
|
847
853
|
for (const shard of event.shards) {
|
848
|
-
|
854
|
+
MutableHashSet.remove(selfShards, shard)
|
849
855
|
}
|
850
856
|
yield* activeShardsLatch.open
|
851
857
|
}
|
@@ -896,20 +902,20 @@ const make = Effect.gen(function*() {
|
|
896
902
|
for (const [shardId, runner] of assignments) {
|
897
903
|
if (Option.isNone(runner)) {
|
898
904
|
MutableHashMap.remove(shardAssignments, shardId)
|
899
|
-
|
905
|
+
MutableHashSet.remove(selfShards, shardId)
|
900
906
|
continue
|
901
907
|
}
|
902
908
|
|
903
909
|
MutableHashMap.set(shardAssignments, shardId, runner.value)
|
904
910
|
|
905
911
|
if (!isLocalRunner(runner.value)) {
|
906
|
-
|
912
|
+
MutableHashSet.remove(selfShards, shardId)
|
907
913
|
continue
|
908
914
|
}
|
909
|
-
if (MutableRef.get(isShutdown) ||
|
915
|
+
if (MutableRef.get(isShutdown) || MutableHashSet.has(selfShards, shardId)) {
|
910
916
|
continue
|
911
917
|
}
|
912
|
-
|
918
|
+
MutableHashSet.add(selfShards, shardId)
|
913
919
|
}
|
914
920
|
|
915
921
|
yield* activeShardsLatch.open
|
@@ -1054,7 +1060,7 @@ const make = Effect.gen(function*() {
|
|
1054
1060
|
return {
|
1055
1061
|
...wrappedClient,
|
1056
1062
|
[currentClientAddress]: ClientAddressTag.context(EntityAddress.make({
|
1057
|
-
shardId: getShardId(id),
|
1063
|
+
shardId: getShardId(id, entity.getShardGroup(entityId as EntityId)),
|
1058
1064
|
entityId: id,
|
1059
1065
|
entityType: entity.type
|
1060
1066
|
}))
|
package/src/ShardingConfig.ts
CHANGED
@@ -20,22 +20,35 @@ import { RunnerAddress } from "./RunnerAddress.js"
|
|
20
20
|
*/
|
21
21
|
export class ShardingConfig extends Context.Tag("@effect/cluster/ShardingConfig")<ShardingConfig, {
|
22
22
|
/**
|
23
|
-
* The address for the current runner
|
23
|
+
* The address for the current runner that other runners can use to
|
24
|
+
* communicate with it.
|
24
25
|
*
|
25
26
|
* If `None`, the runner is not part of the cluster and will be in a client-only
|
26
27
|
* mode.
|
27
28
|
*/
|
28
29
|
readonly runnerAddress: Option.Option<RunnerAddress>
|
30
|
+
/**
|
31
|
+
* The listen address for the current runner.
|
32
|
+
*
|
33
|
+
* Defaults to the `runnerAddress`.
|
34
|
+
*/
|
35
|
+
readonly runnerListenAddress: Option.Option<RunnerAddress>
|
29
36
|
/**
|
30
37
|
* The version of the current runner.
|
31
38
|
*/
|
32
39
|
readonly serverVersion: number
|
33
40
|
/**
|
34
|
-
* The
|
41
|
+
* The shard groups that are assigned to this runner.
|
42
|
+
*
|
43
|
+
* Defaults to `["default"]`.
|
44
|
+
*/
|
45
|
+
readonly shardGroups: ReadonlyArray<string>
|
46
|
+
/**
|
47
|
+
* The number of shards to allocate per shard group.
|
35
48
|
*
|
36
49
|
* **Note**: this value should be consistent across all runners.
|
37
50
|
*/
|
38
|
-
readonly
|
51
|
+
readonly shardsPerGroup: number
|
39
52
|
/**
|
40
53
|
* The address of the shard manager.
|
41
54
|
*/
|
@@ -89,10 +102,12 @@ const defaultRunnerAddress = RunnerAddress.make({ host: "localhost", port: 34431
|
|
89
102
|
*/
|
90
103
|
export const defaults: ShardingConfig["Type"] = {
|
91
104
|
runnerAddress: Option.some(defaultRunnerAddress),
|
105
|
+
runnerListenAddress: Option.none(),
|
92
106
|
serverVersion: 1,
|
93
|
-
|
107
|
+
shardsPerGroup: 300,
|
94
108
|
shardManagerAddress: RunnerAddress.make({ host: "localhost", port: 8080 }),
|
95
109
|
shardManagerUnavailableTimeout: Duration.minutes(10),
|
110
|
+
shardGroups: ["default"],
|
96
111
|
entityMailboxCapacity: 4096,
|
97
112
|
entityMaxIdleTime: Duration.minutes(1),
|
98
113
|
entityTerminationTimeout: Duration.seconds(15),
|
@@ -131,13 +146,27 @@ export const config: Config.Config<ShardingConfig["Type"]> = Config.all({
|
|
131
146
|
Config.withDescription("The port used for inter-runner communication.")
|
132
147
|
)
|
133
148
|
}).pipe(Config.map((options) => RunnerAddress.make(options)), Config.option),
|
149
|
+
runnerListenAddress: Config.all({
|
150
|
+
host: Config.string("listenHost").pipe(
|
151
|
+
Config.withDefault(defaultRunnerAddress.host),
|
152
|
+
Config.withDescription("The host to listen on.")
|
153
|
+
),
|
154
|
+
port: Config.integer("listenPort").pipe(
|
155
|
+
Config.withDefault(defaultRunnerAddress.port),
|
156
|
+
Config.withDescription("The port to listen on.")
|
157
|
+
)
|
158
|
+
}).pipe(Config.map((options) => RunnerAddress.make(options)), Config.option),
|
134
159
|
serverVersion: Config.integer("serverVersion").pipe(
|
135
160
|
Config.withDefault(defaults.serverVersion),
|
136
161
|
Config.withDescription("The version of the current runner.")
|
137
162
|
),
|
138
|
-
|
139
|
-
Config.withDefault(
|
140
|
-
Config.withDescription("The
|
163
|
+
shardGroups: Config.array(Config.string("shardGroups")).pipe(
|
164
|
+
Config.withDefault(["default"]),
|
165
|
+
Config.withDescription("The shard groups that are assigned to this runner.")
|
166
|
+
),
|
167
|
+
shardsPerGroup: Config.integer("shardsPerGroup").pipe(
|
168
|
+
Config.withDefault(defaults.shardsPerGroup),
|
169
|
+
Config.withDescription("The number of shards to allocate per shard group.")
|
141
170
|
),
|
142
171
|
shardManagerAddress: Config.all({
|
143
172
|
host: Config.string("shardManagerHost").pipe(
|
package/src/Singleton.ts
CHANGED
@@ -12,9 +12,12 @@ import { Sharding } from "./Sharding.js"
|
|
12
12
|
*/
|
13
13
|
export const make = <E, R>(
|
14
14
|
name: string,
|
15
|
-
run: Effect.Effect<void, E, R
|
15
|
+
run: Effect.Effect<void, E, R>,
|
16
|
+
options?: {
|
17
|
+
readonly shardGroup?: string | undefined
|
18
|
+
}
|
16
19
|
): Layer.Layer<never, never, Sharding | Exclude<R, Scope>> =>
|
17
20
|
Layer.effectDiscard(Effect.gen(function*() {
|
18
21
|
const sharding = yield* Sharding
|
19
|
-
yield* sharding.registerSingleton(name, run)
|
22
|
+
yield* sharding.registerSingleton(name, run, options)
|
20
23
|
}))
|
package/src/SingletonAddress.ts
CHANGED
@@ -36,12 +36,12 @@ export class SingletonAddress extends Schema.Class<SingletonAddress>("@effect/cl
|
|
36
36
|
* @since 1.0.0
|
37
37
|
*/
|
38
38
|
[Hash.symbol]() {
|
39
|
-
return Hash.cached(this)(Hash.string(`${this.
|
39
|
+
return Hash.cached(this)(Hash.string(`${this.name}:${this.shardId.toString()}`))
|
40
40
|
}
|
41
41
|
/**
|
42
42
|
* @since 1.0.0
|
43
43
|
*/
|
44
44
|
[Equal.symbol](that: SingletonAddress): boolean {
|
45
|
-
return this.
|
45
|
+
return this.name === that.name && this.shardId[Equal.symbol](that.shardId)
|
46
46
|
}
|
47
47
|
}
|
package/src/SqlMessageStorage.ts
CHANGED
@@ -15,6 +15,7 @@ import type * as Envelope from "./Envelope.js"
|
|
15
15
|
import * as MessageStorage from "./MessageStorage.js"
|
16
16
|
import { SaveResultEncoded } from "./MessageStorage.js"
|
17
17
|
import type * as Reply from "./Reply.js"
|
18
|
+
import { ShardId } from "./ShardId.js"
|
18
19
|
import type { ShardingConfig } from "./ShardingConfig.js"
|
19
20
|
import * as Snowflake from "./Snowflake.js"
|
20
21
|
|
@@ -48,15 +49,15 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
48
49
|
id BIGINT PRIMARY KEY,
|
49
50
|
rowid BIGINT IDENTITY(1,1),
|
50
51
|
message_id VARCHAR(255),
|
51
|
-
shard_id
|
52
|
-
entity_type VARCHAR(
|
52
|
+
shard_id VARCHAR(50) NOT NULL,
|
53
|
+
entity_type VARCHAR(50) NOT NULL,
|
53
54
|
entity_id VARCHAR(255) NOT NULL,
|
54
55
|
kind INT NOT NULL,
|
55
|
-
tag VARCHAR(
|
56
|
+
tag VARCHAR(50),
|
56
57
|
payload TEXT,
|
57
58
|
headers TEXT,
|
58
|
-
trace_id VARCHAR(
|
59
|
-
span_id VARCHAR(
|
59
|
+
trace_id VARCHAR(32),
|
60
|
+
span_id VARCHAR(16),
|
60
61
|
sampled BIT,
|
61
62
|
processed BIT NOT NULL DEFAULT 0,
|
62
63
|
request_id BIGINT NOT NULL,
|
@@ -74,15 +75,15 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
74
75
|
id BIGINT NOT NULL,
|
75
76
|
rowid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
76
77
|
message_id VARCHAR(255),
|
77
|
-
shard_id
|
78
|
-
entity_type VARCHAR(
|
78
|
+
shard_id VARCHAR(50) NOT NULL,
|
79
|
+
entity_type VARCHAR(50) NOT NULL,
|
79
80
|
entity_id VARCHAR(255) NOT NULL,
|
80
81
|
kind INT NOT NULL,
|
81
|
-
tag VARCHAR(
|
82
|
+
tag VARCHAR(50),
|
82
83
|
payload TEXT,
|
83
84
|
headers TEXT,
|
84
|
-
trace_id VARCHAR(
|
85
|
-
span_id VARCHAR(
|
85
|
+
trace_id VARCHAR(32),
|
86
|
+
span_id VARCHAR(16),
|
86
87
|
sampled BOOLEAN,
|
87
88
|
processed BOOLEAN NOT NULL DEFAULT FALSE,
|
88
89
|
request_id BIGINT NOT NULL,
|
@@ -101,15 +102,15 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
101
102
|
id BIGINT PRIMARY KEY,
|
102
103
|
rowid BIGSERIAL,
|
103
104
|
message_id VARCHAR(255),
|
104
|
-
shard_id
|
105
|
-
entity_type VARCHAR(
|
105
|
+
shard_id VARCHAR(50) NOT NULL,
|
106
|
+
entity_type VARCHAR(50) NOT NULL,
|
106
107
|
entity_id VARCHAR(255) NOT NULL,
|
107
108
|
kind INT NOT NULL,
|
108
|
-
tag VARCHAR(
|
109
|
+
tag VARCHAR(50),
|
109
110
|
payload TEXT,
|
110
111
|
headers TEXT,
|
111
|
-
trace_id VARCHAR(
|
112
|
-
span_id VARCHAR(
|
112
|
+
trace_id VARCHAR(32),
|
113
|
+
span_id VARCHAR(16),
|
113
114
|
sampled BOOLEAN,
|
114
115
|
processed BOOLEAN NOT NULL DEFAULT FALSE,
|
115
116
|
request_id BIGINT NOT NULL,
|
@@ -127,7 +128,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
127
128
|
CREATE TABLE IF NOT EXISTS ${messagesTableSql} (
|
128
129
|
id INTEGER PRIMARY KEY,
|
129
130
|
message_id TEXT,
|
130
|
-
shard_id
|
131
|
+
shard_id TEXT NOT NULL,
|
131
132
|
entity_type TEXT NOT NULL,
|
132
133
|
entity_id TEXT NOT NULL,
|
133
134
|
kind INTEGER NOT NULL,
|
@@ -315,7 +316,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
315
316
|
return {
|
316
317
|
id: envelope.requestId,
|
317
318
|
message_id,
|
318
|
-
shard_id: envelope.address.shardId,
|
319
|
+
shard_id: ShardId.toString(envelope.address.shardId),
|
319
320
|
entity_type: envelope.address.entityType,
|
320
321
|
entity_id: envelope.address.entityId,
|
321
322
|
kind: messageKind.Request,
|
@@ -333,7 +334,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
333
334
|
return {
|
334
335
|
id: envelope.id,
|
335
336
|
message_id,
|
336
|
-
shard_id: envelope.address.shardId,
|
337
|
+
shard_id: ShardId.toString(envelope.address.shardId),
|
337
338
|
entity_type: envelope.address.entityType,
|
338
339
|
entity_id: envelope.address.entityId,
|
339
340
|
kind: messageKind.AckChunk,
|
@@ -351,7 +352,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
351
352
|
return {
|
352
353
|
id: envelope.id,
|
353
354
|
message_id,
|
354
|
-
shard_id: envelope.address.shardId,
|
355
|
+
shard_id: ShardId.toString(envelope.address.shardId),
|
355
356
|
entity_type: envelope.address.entityType,
|
356
357
|
entity_id: envelope.address.entityId,
|
357
358
|
kind: messageKind.Interrupt,
|
@@ -393,7 +394,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
393
394
|
_tag: "Request",
|
394
395
|
requestId: String(row.id),
|
395
396
|
address: {
|
396
|
-
shardId:
|
397
|
+
shardId: ShardId.fromStringEncoded(row.shard_id),
|
397
398
|
entityType: row.entity_type,
|
398
399
|
entityId: row.entity_id
|
399
400
|
},
|
@@ -422,7 +423,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
422
423
|
requestId: String(row.request_id!),
|
423
424
|
replyId: String(row.reply_id!),
|
424
425
|
address: {
|
425
|
-
shardId:
|
426
|
+
shardId: ShardId.fromStringEncoded(row.shard_id),
|
426
427
|
entityType: row.entity_type,
|
427
428
|
entityId: row.entity_id
|
428
429
|
}
|
@@ -436,7 +437,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
436
437
|
id: String(row.id),
|
437
438
|
requestId: String(row.request_id!),
|
438
439
|
address: {
|
439
|
-
shardId:
|
440
|
+
shardId: ShardId.fromStringEncoded(row.shard_id),
|
440
441
|
entityType: row.entity_type,
|
441
442
|
entityId: row.entity_id
|
442
443
|
}
|
@@ -549,15 +550,20 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
549
550
|
})
|
550
551
|
const sqlNow = sql.literal(sqlNowString)
|
551
552
|
|
553
|
+
const wrapString = sql.onDialectOrElse({
|
554
|
+
mssql: () => (s: string) => `N'${s}'`,
|
555
|
+
orElse: () => (s: string) => `'${s}'`
|
556
|
+
})
|
557
|
+
|
552
558
|
const getUnprocessedMessages = sql.onDialectOrElse({
|
553
|
-
pg: () => (shardIds: ReadonlyArray<
|
559
|
+
pg: () => (shardIds: ReadonlyArray<string>, now: number) =>
|
554
560
|
sql<MessageJoinRow>`
|
555
561
|
UPDATE ${messagesTableSql} m
|
556
562
|
SET last_read = ${sqlNow}
|
557
563
|
FROM (
|
558
564
|
SELECT m.*
|
559
565
|
FROM ${messagesTableSql} m
|
560
|
-
WHERE m.shard_id IN (${sql.literal(shardIds.map(
|
566
|
+
WHERE m.shard_id IN (${sql.literal(shardIds.map(wrapString).join(","))})
|
561
567
|
AND NOT EXISTS (
|
562
568
|
SELECT 1 FROM ${repliesTableSql}
|
563
569
|
WHERE request_id = m.request_id
|
@@ -573,12 +579,12 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
573
579
|
WHERE m.id = ids.id
|
574
580
|
RETURNING ids.*, r.id as reply_reply_id, r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequence
|
575
581
|
`,
|
576
|
-
orElse: () => (shardIds: ReadonlyArray<
|
582
|
+
orElse: () => (shardIds: ReadonlyArray<string>, now: number) =>
|
577
583
|
sql<MessageJoinRow>`
|
578
584
|
SELECT m.*, r.id as reply_reply_id, r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequence
|
579
585
|
FROM ${messagesTableSql} m
|
580
586
|
LEFT JOIN ${repliesTableSql} r ON r.id = m.last_reply_id
|
581
|
-
WHERE m.shard_id IN (${sql.literal(shardIds.map(
|
587
|
+
WHERE m.shard_id IN (${sql.literal(shardIds.map(wrapString).join(","))})
|
582
588
|
AND NOT EXISTS (
|
583
589
|
SELECT 1 FROM ${repliesTableSql}
|
584
590
|
WHERE request_id = m.request_id
|
@@ -653,7 +659,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
653
659
|
const row = replyToRow(reply)
|
654
660
|
const update = reply._tag === "Chunk" ?
|
655
661
|
sql`UPDATE ${messagesTableSql} SET last_reply_id = ${reply.id} WHERE id = ${reply.requestId}` :
|
656
|
-
sql`UPDATE ${messagesTableSql} SET processed = ${sqlTrue}, last_reply_id =
|
662
|
+
sql`UPDATE ${messagesTableSql} SET processed = ${sqlTrue}, last_reply_id = ${reply.id} WHERE request_id = ${reply.requestId}`
|
657
663
|
return update.unprepared.pipe(
|
658
664
|
Effect.andThen(sql`INSERT INTO ${repliesTableSql} ${sql.insert(row)}`),
|
659
665
|
sql.withTransaction
|
@@ -772,7 +778,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
772
778
|
UPDATE ${messagesTableSql}
|
773
779
|
SET last_read = NULL
|
774
780
|
WHERE processed = ${sqlFalse}
|
775
|
-
AND shard_id = ${address.shardId}
|
781
|
+
AND shard_id = ${address.shardId.toString()}
|
776
782
|
AND entity_type = ${address.entityType}
|
777
783
|
AND entity_id = ${address.entityId}
|
778
784
|
`.pipe(
|
@@ -808,7 +814,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
808
814
|
UPDATE ${messagesTableSql}
|
809
815
|
SET last_read = NULL
|
810
816
|
WHERE processed = ${sqlFalse}
|
811
|
-
AND shard_id IN (${sql.literal(shardIds.join(","))})
|
817
|
+
AND shard_id IN (${sql.literal(shardIds.map(wrapString).join(","))})
|
812
818
|
`.pipe(
|
813
819
|
Effect.asVoid,
|
814
820
|
PersistenceError.refail,
|
@@ -875,7 +881,7 @@ const replyFromRow = (row: ReplyRow): Reply.ReplyEncoded<any> =>
|
|
875
881
|
type MessageRow = {
|
876
882
|
readonly id: string | bigint
|
877
883
|
readonly message_id: string | null
|
878
|
-
readonly shard_id:
|
884
|
+
readonly shard_id: string
|
879
885
|
readonly entity_type: string
|
880
886
|
readonly entity_id: string
|
881
887
|
readonly kind: 0 | 1 | 2 | 0n | 1n | 2n
|