adaptive-concurrency 0.10.0 → 0.11.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/dist/Limiter.d.ts +7 -0
- package/dist/Limiter.d.ts.map +1 -1
- package/dist/Limiter.js +5 -2
- package/dist/MetricRegistry.d.ts +3 -0
- package/dist/MetricRegistry.d.ts.map +1 -1
- package/dist/MetricRegistry.js +3 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/limit/AIMDLimit.d.ts +12 -1
- package/dist/limit/AIMDLimit.d.ts.map +1 -1
- package/dist/limit/AIMDLimit.js +12 -3
- package/dist/limit/FixedLimit.d.ts +1 -1
- package/dist/limit/FixedLimit.d.ts.map +1 -1
- package/dist/limit/FixedLimit.js +1 -1
- package/dist/limit/GradientLimit.d.ts +1 -1
- package/dist/limit/GradientLimit.d.ts.map +1 -1
- package/dist/limit/GradientLimit.js +1 -1
- package/dist/limit/GroupAwareLimit.d.ts +77 -0
- package/dist/limit/GroupAwareLimit.d.ts.map +1 -0
- package/dist/limit/GroupAwareLimit.js +208 -0
- package/dist/limit/OperationGroupedLimit.d.ts +78 -0
- package/dist/limit/OperationGroupedLimit.d.ts.map +1 -0
- package/dist/limit/OperationGroupedLimit.js +93 -0
- package/dist/limit/SettableLimit.d.ts +1 -1
- package/dist/limit/SettableLimit.d.ts.map +1 -1
- package/dist/limit/SettableLimit.js +1 -1
- package/dist/limit/StreamingLimit.d.ts +3 -1
- package/dist/limit/StreamingLimit.d.ts.map +1 -1
- package/dist/limit/TracingLimitDecorator.d.ts +1 -1
- package/dist/limit/TracingLimitDecorator.d.ts.map +1 -1
- package/dist/limit/TracingLimitDecorator.js +2 -2
- package/dist/limit/VegasLimit.d.ts +5 -4
- package/dist/limit/VegasLimit.d.ts.map +1 -1
- package/dist/limit/VegasLimit.js +12 -17
- package/dist/limit/WindowedLimit.d.ts +8 -1
- package/dist/limit/WindowedLimit.d.ts.map +1 -1
- package/dist/limit/WindowedLimit.js +16 -4
- package/dist/limit/window/PercentileSampleWindow.d.ts.map +1 -1
- package/dist/limit/window/PercentileSampleWindow.js +40 -4
- package/dist/statistics/DecayingHistogram.d.ts +63 -0
- package/dist/statistics/DecayingHistogram.d.ts.map +1 -0
- package/dist/statistics/DecayingHistogram.js +144 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +13 -0
- package/package.json +4 -1
package/dist/Limiter.d.ts
CHANGED
|
@@ -98,6 +98,12 @@ export interface LimiterOptions<ContextT> {
|
|
|
98
98
|
* request. When omitted, rejected requests immediately receive `undefined`.
|
|
99
99
|
*/
|
|
100
100
|
allotmentUnavailableStrategy?: AllotmentUnavailableStrategy<ContextT>;
|
|
101
|
+
/**
|
|
102
|
+
* Derives an operation name from the request context, passed to the limit
|
|
103
|
+
* algorithm's `addSample` so group-aware limits can distinguish
|
|
104
|
+
* heterogeneous workloads. When omitted, no operation name is provided.
|
|
105
|
+
*/
|
|
106
|
+
operationNameFor?: (context: ContextT) => string | undefined;
|
|
101
107
|
}
|
|
102
108
|
/**
|
|
103
109
|
* Concurrency limiter with pluggable strategies for gating decisions and
|
|
@@ -113,6 +119,7 @@ export declare class Limiter<Context = void> {
|
|
|
113
119
|
private readonly acquireStrategy;
|
|
114
120
|
private readonly rejectionStrategy;
|
|
115
121
|
private readonly bypassResolver;
|
|
122
|
+
private readonly operationNameFor;
|
|
116
123
|
private readonly acquireBypassedAllotment;
|
|
117
124
|
private readonly successCounter;
|
|
118
125
|
private readonly droppedCounter;
|
package/dist/Limiter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Limiter.d.ts","sourceRoot":"","sources":["../src/Limiter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAIV,cAAc,EACf,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAGL,iBAAiB,EACjB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;AAEhE,MAAM,WAAW,cAAc,CAAC,QAAQ,GAAG,IAAI;IAC7C,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAClC;AAMD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe,CAAC,QAAQ;IACvC;;;;OAIG;IACH,mBAAmB,CACjB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,YAAY,GAClB,YAAY,CAAC,OAAO,CAAC,CAAC;IAEzB;;;OAGG;IACH,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAE3D;;;OAGG;IACH,cAAc,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3D;AAED;;;;;GAKG;AACH,MAAM,WAAW,4BAA4B,CAAC,QAAQ;IACpD;;;;;;;;;;OAUG;IACH,sBAAsB,CACpB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,EAC3C,MAAM,CAAC,EAAE,WAAW,GACnB,aAAa,CAAC;IAEjB;;;OAGG;IACH,mBAAmB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IAE1C;;;;OAIG;IACH,cAAc,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;CACzE;AAOD,MAAM,WAAW,cAAc,CAAC,QAAQ;IACtC,KAAK,CAAC,EAAE,aAAa,CAAC;IAEtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IAErB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;IAEhD;;;OAGG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAE5C;;;OAGG;IACH,4BAA4B,CAAC,EAAE,4BAA4B,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"Limiter.d.ts","sourceRoot":"","sources":["../src/Limiter.ts"],"names":[],"mappings":"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"}
|
package/dist/Limiter.js
CHANGED
|
@@ -20,6 +20,7 @@ export class Limiter {
|
|
|
20
20
|
acquireStrategy;
|
|
21
21
|
rejectionStrategy;
|
|
22
22
|
bypassResolver;
|
|
23
|
+
operationNameFor;
|
|
23
24
|
acquireBypassedAllotment;
|
|
24
25
|
successCounter;
|
|
25
26
|
droppedCounter;
|
|
@@ -38,6 +39,7 @@ export class Limiter {
|
|
|
38
39
|
this.limitAlgorithm = options.limit ?? Limiter.makeDefaultLimit();
|
|
39
40
|
this._limit = this.limitAlgorithm.currentLimit;
|
|
40
41
|
this.bypassResolver = options.bypassResolver;
|
|
42
|
+
this.operationNameFor = options.operationNameFor;
|
|
41
43
|
this.acquireStrategy =
|
|
42
44
|
options.acquireStrategy ?? new SemaphoreStrategy(this._limit);
|
|
43
45
|
this.rejectionStrategy = options.allotmentUnavailableStrategy;
|
|
@@ -148,6 +150,7 @@ export class Limiter {
|
|
|
148
150
|
createAllotment(ctx) {
|
|
149
151
|
const startTime = this.clock();
|
|
150
152
|
const currentInflight = ++this._inflight;
|
|
153
|
+
const operationName = this.operationNameFor?.(ctx);
|
|
151
154
|
// Make sure an allotment can only be released once; future calls become a
|
|
152
155
|
// no-op. This simplifies a lot of cleanup handling etc that'd otherwise be
|
|
153
156
|
// much racier/more complicated. It could hide subtle correctness issue, but
|
|
@@ -170,7 +173,7 @@ export class Limiter {
|
|
|
170
173
|
}
|
|
171
174
|
catch { }
|
|
172
175
|
try {
|
|
173
|
-
this.limitAlgorithm.addSample(startTime, rtt, currentInflight, false);
|
|
176
|
+
this.limitAlgorithm.addSample(startTime, rtt, currentInflight, false, operationName);
|
|
174
177
|
}
|
|
175
178
|
catch { }
|
|
176
179
|
try {
|
|
@@ -212,7 +215,7 @@ export class Limiter {
|
|
|
212
215
|
}
|
|
213
216
|
catch { }
|
|
214
217
|
try {
|
|
215
|
-
this.limitAlgorithm.addSample(startTime, rtt, currentInflight, true);
|
|
218
|
+
this.limitAlgorithm.addSample(startTime, rtt, currentInflight, true, operationName);
|
|
216
219
|
}
|
|
217
220
|
catch { }
|
|
218
221
|
try {
|
package/dist/MetricRegistry.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ export declare const MetricIds: {
|
|
|
16
16
|
readonly WINDOW_MIN_RTT_NAME: "min_window_rtt";
|
|
17
17
|
readonly WINDOW_QUEUE_SIZE_NAME: "queue_size";
|
|
18
18
|
readonly ACQUIRE_TIME_NAME: "acquire_time";
|
|
19
|
+
readonly CONGESTION_SIGNAL_NAME: "congestion_signal";
|
|
20
|
+
readonly WARMED_GROUPS_COUNT_NAME: "warmed_groups_count";
|
|
21
|
+
readonly GROUP_RTT_RATIO_NAME: "group_rtt_ratio";
|
|
19
22
|
};
|
|
20
23
|
/**
|
|
21
24
|
* Listener to receive samples for a distribution.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricRegistry.d.ts","sourceRoot":"","sources":["../src/MetricRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS
|
|
1
|
+
{"version":3,"file":"MetricRegistry.d.ts","sourceRoot":"","sources":["../src/MetricRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;CAaZ,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,0FAA0F;AAC1F,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,YAAY,CACV,EAAE,EAAE,MAAM,EACV,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,kBAAkB,CAAC;IAEtB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IAE9D;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;CACnE;AAMD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAUhC,CAAC"}
|
package/dist/MetricRegistry.js
CHANGED
|
@@ -16,6 +16,9 @@ export const MetricIds = {
|
|
|
16
16
|
WINDOW_MIN_RTT_NAME: "min_window_rtt",
|
|
17
17
|
WINDOW_QUEUE_SIZE_NAME: "queue_size",
|
|
18
18
|
ACQUIRE_TIME_NAME: "acquire_time",
|
|
19
|
+
CONGESTION_SIGNAL_NAME: "congestion_signal",
|
|
20
|
+
WARMED_GROUPS_COUNT_NAME: "warmed_groups_count",
|
|
21
|
+
GROUP_RTT_RATIO_NAME: "group_rtt_ratio",
|
|
19
22
|
};
|
|
20
23
|
const NOOP_SAMPLE_LISTENER = { addSample() { } };
|
|
21
24
|
const NOOP_COUNTER = { increment() { } };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,17 +7,18 @@ export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, isRunRes
|
|
|
7
7
|
export { AIMDLimit, type AIMDLimitOptions } from "./limit/AIMDLimit.js";
|
|
8
8
|
export { FixedLimit } from "./limit/FixedLimit.js";
|
|
9
9
|
export { GradientLimit, type Gradient2LimitOptions, } from "./limit/GradientLimit.js";
|
|
10
|
+
export { GroupAwareLimit } from "./limit/GroupAwareLimit.js";
|
|
10
11
|
export { SettableLimit } from "./limit/SettableLimit.js";
|
|
11
|
-
export { TracingLimitDecorator } from "./limit/TracingLimitDecorator.js";
|
|
12
12
|
export { VegasLimit, type VegasLimitOptions, type VegasLimitPolicy, } from "./limit/VegasLimit.js";
|
|
13
13
|
export { WindowedLimit, type WindowedLimitOptions, } from "./limit/WindowedLimit.js";
|
|
14
|
+
export { DecayingHistogram } from "./statistics/DecayingHistogram.js";
|
|
14
15
|
export { ExpMovingAverage } from "./statistics/ExpMovingAverage.js";
|
|
15
16
|
export { MinimumValue } from "./statistics/MinimumValue.js";
|
|
16
17
|
export type { StreamingStatistic } from "./statistics/StreamingStatistic.js";
|
|
17
18
|
export { makeAverageSampleWindow } from "./limit/window/AverageSampleWindow.js";
|
|
18
19
|
export { createPercentileSampleWindow } from "./limit/window/PercentileSampleWindow.js";
|
|
19
20
|
export type { SampleWindow } from "./limit/window/SampleWindow.js";
|
|
20
|
-
export { LinkedWaiterQueue, squareRoot, squareRootWithBaseline, } from "./utils/index.js";
|
|
21
|
+
export { LinkedWaiterQueue, log10Scale, squareRoot, squareRootWithBaseline, } from "./utils/index.js";
|
|
21
22
|
export * from "./limiter/factories/index.js";
|
|
22
23
|
export { PartitionedStrategy, type PartitionConfig, } from "./limiter/acquire-strategies/PartitionedStrategy.js";
|
|
23
24
|
export { SemaphoreStrategy } from "./limiter/acquire-strategies/SemaphoreStrategy.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,OAAO,EACP,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,KAAK,EACV,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EACL,aAAa,EACb,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,OAAO,EACP,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,KAAK,EACV,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EACL,aAAa,EACb,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EACL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,GAC1B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAG7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;AACxF,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAEnE,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAE1B,cAAc,8BAA8B,CAAC;AAG7C,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,qDAAqD,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mDAAmD,CAAC;AAGtF,OAAO,EACL,wBAAwB,EACxB,WAAW,EACX,KAAK,+BAA+B,EACpC,KAAK,WAAW,GACjB,MAAM,yEAAyE,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sEAAsE,CAAC;AAC9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,6EAA6E,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,17 +6,18 @@ export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, isRunRes
|
|
|
6
6
|
export { AIMDLimit } from "./limit/AIMDLimit.js";
|
|
7
7
|
export { FixedLimit } from "./limit/FixedLimit.js";
|
|
8
8
|
export { GradientLimit, } from "./limit/GradientLimit.js";
|
|
9
|
+
export { GroupAwareLimit } from "./limit/GroupAwareLimit.js";
|
|
9
10
|
export { SettableLimit } from "./limit/SettableLimit.js";
|
|
10
|
-
export { TracingLimitDecorator } from "./limit/TracingLimitDecorator.js";
|
|
11
11
|
export { VegasLimit, } from "./limit/VegasLimit.js";
|
|
12
12
|
export { WindowedLimit, } from "./limit/WindowedLimit.js";
|
|
13
13
|
// Streaming statistics
|
|
14
|
+
export { DecayingHistogram } from "./statistics/DecayingHistogram.js";
|
|
14
15
|
export { ExpMovingAverage } from "./statistics/ExpMovingAverage.js";
|
|
15
16
|
export { MinimumValue } from "./statistics/MinimumValue.js";
|
|
16
17
|
// Sample window types
|
|
17
18
|
export { makeAverageSampleWindow } from "./limit/window/AverageSampleWindow.js";
|
|
18
19
|
export { createPercentileSampleWindow } from "./limit/window/PercentileSampleWindow.js";
|
|
19
|
-
export { LinkedWaiterQueue, squareRoot, squareRootWithBaseline, } from "./utils/index.js";
|
|
20
|
+
export { LinkedWaiterQueue, log10Scale, squareRoot, squareRootWithBaseline, } from "./utils/index.js";
|
|
20
21
|
export * from "./limiter/factories/index.js";
|
|
21
22
|
// Acquire strategies
|
|
22
23
|
export { PartitionedStrategy, } from "./limiter/acquire-strategies/PartitionedStrategy.js";
|
|
@@ -17,6 +17,16 @@ export interface AIMDLimitOptions {
|
|
|
17
17
|
* Default: 5000
|
|
18
18
|
*/
|
|
19
19
|
timeout?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Absolute amount to use as a +/- jitter band around `backoffRatio` for
|
|
22
|
+
* each multiplicative decrease. Breaks lockstep oscillation when multiple
|
|
23
|
+
* independent clients share the same configuration.
|
|
24
|
+
*
|
|
25
|
+
* For example, with `backoffRatio: 0.9` and `backoffJitter: 0.02`, each
|
|
26
|
+
* decrease multiplies by a uniformly random value in [0.88, 0.92].
|
|
27
|
+
* Must be in [0, 0.05]. Default: 0.02.
|
|
28
|
+
*/
|
|
29
|
+
backoffJitter?: number;
|
|
20
30
|
}
|
|
21
31
|
export declare class AIMDLimit implements AdaptiveLimit {
|
|
22
32
|
private _limit;
|
|
@@ -25,8 +35,9 @@ export declare class AIMDLimit implements AdaptiveLimit {
|
|
|
25
35
|
private readonly timeout;
|
|
26
36
|
private readonly minLimit;
|
|
27
37
|
private readonly maxLimit;
|
|
38
|
+
private readonly backoffJitter;
|
|
28
39
|
constructor(options?: AIMDLimitOptions);
|
|
29
|
-
addSample(_startTime: number, rtt: number, inflight: number, didDrop: boolean): void;
|
|
40
|
+
addSample(_startTime: number, rtt: number, inflight: number, didDrop: boolean, _operationName?: string): void;
|
|
30
41
|
get currentLimit(): number;
|
|
31
42
|
private applyNewLimit;
|
|
32
43
|
subscribe(consumer: (newLimit: number) => void, options?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIMDLimit.d.ts","sourceRoot":"","sources":["../../src/limit/AIMDLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"AIMDLimit.d.ts","sourceRoot":"","sources":["../../src/limit/AIMDLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,SAAU,YAAW,aAAa;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,OAAO,GAAE,gBAAqB;IA0B1C,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI;IAuBP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,aAAa;IAOrB,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,QAAQ,IAAI,MAAM;CAGnB"}
|
package/dist/limit/AIMDLimit.js
CHANGED
|
@@ -6,6 +6,7 @@ export class AIMDLimit {
|
|
|
6
6
|
timeout;
|
|
7
7
|
minLimit;
|
|
8
8
|
maxLimit;
|
|
9
|
+
backoffJitter;
|
|
9
10
|
constructor(options = {}) {
|
|
10
11
|
const initialLimit = options.initialLimit ?? 20;
|
|
11
12
|
this._limit = initialLimit;
|
|
@@ -13,19 +14,27 @@ export class AIMDLimit {
|
|
|
13
14
|
this.timeout = options.timeout ?? 5_000;
|
|
14
15
|
this.minLimit = options.minLimit ?? 20;
|
|
15
16
|
this.maxLimit = options.maxLimit ?? 200;
|
|
17
|
+
this.backoffJitter = options.backoffJitter ?? 0.02;
|
|
16
18
|
if (this.backoffRatio >= 1.0 || this.backoffRatio < 0.5) {
|
|
17
19
|
throw new Error("Backoff ratio must be in the range [0.5, 1.0)");
|
|
18
20
|
}
|
|
19
21
|
if (this.timeout <= 0) {
|
|
20
22
|
throw new Error("Timeout must be positive");
|
|
21
23
|
}
|
|
24
|
+
if (this.backoffJitter < 0 || this.backoffJitter > 0.05) {
|
|
25
|
+
throw new Error("backoffJitter must be in the range [0, 0.05]");
|
|
26
|
+
}
|
|
27
|
+
if (this.backoffRatio + this.backoffJitter >= 1.0) {
|
|
28
|
+
throw new Error("backoffRatio + backoffJitter must be < 1.0 to guarantee the limit decreases on drop");
|
|
29
|
+
}
|
|
22
30
|
}
|
|
23
|
-
addSample(_startTime, rtt, inflight, didDrop) {
|
|
31
|
+
addSample(_startTime, rtt, inflight, didDrop, _operationName) {
|
|
24
32
|
let currentLimit = this._limit;
|
|
25
33
|
if (didDrop || rtt > this.timeout) {
|
|
26
|
-
|
|
34
|
+
const jitteredRatio = Math.max(0.5, Math.min(1 - Number.EPSILON, this.backoffRatio + (Math.random() * 2 - 1) * this.backoffJitter));
|
|
35
|
+
currentLimit = Math.floor(currentLimit * jitteredRatio);
|
|
27
36
|
}
|
|
28
|
-
else if (inflight
|
|
37
|
+
else if (inflight >= 0.5 * currentLimit) {
|
|
29
38
|
currentLimit = currentLimit + 1;
|
|
30
39
|
}
|
|
31
40
|
const newLimit = Math.min(this.maxLimit, Math.max(this.minLimit, currentLimit));
|
|
@@ -5,7 +5,7 @@ import type { AdaptiveLimit } from "./StreamingLimit.js";
|
|
|
5
5
|
export declare class FixedLimit implements AdaptiveLimit {
|
|
6
6
|
private _limit;
|
|
7
7
|
constructor(limit: number);
|
|
8
|
-
addSample(_startTime: number, _rtt: number, _inflight: number, _didDrop: boolean): void;
|
|
8
|
+
addSample(_startTime: number, _rtt: number, _inflight: number, _didDrop: boolean, _operationName?: string): void;
|
|
9
9
|
get currentLimit(): number;
|
|
10
10
|
subscribe(_consumer: (newLimit: number) => void, _options?: {
|
|
11
11
|
signal?: AbortSignal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FixedLimit.d.ts","sourceRoot":"","sources":["../../src/limit/FixedLimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD;;GAEG;AACH,qBAAa,UAAW,YAAW,aAAa;IAC9C,OAAO,CAAC,MAAM,CAAS;gBAEX,KAAK,EAAE,MAAM;IAIzB,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"FixedLimit.d.ts","sourceRoot":"","sources":["../../src/limit/FixedLimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD;;GAEG;AACH,qBAAa,UAAW,YAAW,aAAa;IAC9C,OAAO,CAAC,MAAM,CAAS;gBAEX,KAAK,EAAE,MAAM;IAIzB,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI;IAGP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CACP,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACrC,QAAQ,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACtC,MAAM,IAAI;IAMb,QAAQ,IAAI,MAAM;CAGnB"}
|
package/dist/limit/FixedLimit.js
CHANGED
|
@@ -108,7 +108,7 @@ export declare class GradientLimit implements AdaptiveLimit {
|
|
|
108
108
|
private readonly shortRttSampleListener;
|
|
109
109
|
private readonly queueSizeSampleListener;
|
|
110
110
|
constructor(options?: Gradient2LimitOptions);
|
|
111
|
-
addSample(_startTime: number, rtt: number, inflight: number, _didDrop: boolean): void;
|
|
111
|
+
addSample(_startTime: number, rtt: number, inflight: number, _didDrop: boolean, _operationName?: string): void;
|
|
112
112
|
get currentLimit(): number;
|
|
113
113
|
private applyNewLimit;
|
|
114
114
|
subscribe(consumer: (newLimit: number) => void, options?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GradientLimit.d.ts","sourceRoot":"","sources":["../../src/limit/GradientLimit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,WAAW,qBAAqB;IACpC,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAEvD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,qBAAa,aAAc,YAAW,aAAa;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,yDAAyD;IACzD,OAAO,CAAC,cAAc,CAAS;IAE/B;;;OAGG;IACH,OAAO,CAAC,OAAO,CAAK;IAEpB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAE7C,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAC3D,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAqB;IAC5D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqB;gBAEjD,OAAO,GAAE,qBAA0B;IA4B/C,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"GradientLimit.d.ts","sourceRoot":"","sources":["../../src/limit/GradientLimit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,WAAW,qBAAqB;IACpC,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAEvD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,qBAAa,aAAc,YAAW,aAAa;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,yDAAyD;IACzD,OAAO,CAAC,cAAc,CAAS;IAE/B;;;OAGG;IACH,OAAO,CAAC,OAAO,CAAK;IAEpB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAE7C,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAC3D,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAqB;IAC5D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqB;gBAEjD,OAAO,GAAE,qBAA0B;IA4B/C,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,EACjB,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI;IASP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,aAAa;IAOrB,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,OAAO,CAAC,yBAAyB;IA0CjC,UAAU,IAAI,MAAM;IAIpB,YAAY,IAAI,MAAM;IAItB,QAAQ,IAAI,MAAM;CAGnB"}
|
|
@@ -46,7 +46,7 @@ export class GradientLimit {
|
|
|
46
46
|
this.shortRttSampleListener = registry.distribution(MetricIds.WINDOW_MIN_RTT_NAME);
|
|
47
47
|
this.queueSizeSampleListener = registry.distribution(MetricIds.WINDOW_QUEUE_SIZE_NAME);
|
|
48
48
|
}
|
|
49
|
-
addSample(_startTime, rtt, inflight, _didDrop) {
|
|
49
|
+
addSample(_startTime, rtt, inflight, _didDrop, _operationName) {
|
|
50
50
|
const newLimitNoFloor = this.computeNextLimitUnrounded(rtt, inflight);
|
|
51
51
|
this.estimatedLimit = newLimitNoFloor;
|
|
52
52
|
const newLimit = Math.floor(newLimitNoFloor);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { MetricRegistry } from "../MetricRegistry.js";
|
|
2
|
+
import type { AdaptiveLimit } from "./StreamingLimit.js";
|
|
3
|
+
/**
|
|
4
|
+
* Mix-agnostic adaptive concurrency limit that detects congestion using
|
|
5
|
+
* per-group RTT ratios weighted by sample density.
|
|
6
|
+
*
|
|
7
|
+
* Each sample is associated with an operation group via `operationName`. Per-group
|
|
8
|
+
* state tracks a long-lived p10 RTT baseline (decaying histogram) and a short
|
|
9
|
+
* EMA of recent RTT. The congestion signal is the weighted average of
|
|
10
|
+
* `recentRtt / p10` across warmed-up groups, where weight is
|
|
11
|
+
* `sqrt(decayedSampleCount)`.
|
|
12
|
+
*
|
|
13
|
+
* The limit increases additively when the congestion signal is low and inflight
|
|
14
|
+
* is near the limit, and decreases via a configurable `decrease` function when
|
|
15
|
+
* the signal is high or a drop occurs. Before any groups are warmed up, the
|
|
16
|
+
* limit only responds to drops.
|
|
17
|
+
*
|
|
18
|
+
* Samples without an `operationName` do not contribute to any group's state
|
|
19
|
+
* (no histogram, EMA, or activity counter update). However, they still
|
|
20
|
+
* participate in limit decisions: drops always trigger a decrease, and
|
|
21
|
+
* non-drop samples can trigger an increase or decrease based on the current
|
|
22
|
+
* congestion signal from warmed-up groups. This means unnamed operations
|
|
23
|
+
* free-ride on the congestion detection provided by named groups without
|
|
24
|
+
* polluting group baselines.
|
|
25
|
+
*
|
|
26
|
+
* This design is immune to operation mix shifts: a transition from fast to slow
|
|
27
|
+
* operations does not cause a spurious RTT spike because each group is measured
|
|
28
|
+
* against its own baseline.
|
|
29
|
+
*/
|
|
30
|
+
export declare class GroupAwareLimit implements AdaptiveLimit {
|
|
31
|
+
private _limit;
|
|
32
|
+
private readonly limitListeners;
|
|
33
|
+
private readonly groups;
|
|
34
|
+
private readonly minLimit;
|
|
35
|
+
private readonly maxLimit;
|
|
36
|
+
private readonly alpha;
|
|
37
|
+
private readonly beta;
|
|
38
|
+
private readonly decrease;
|
|
39
|
+
private readonly baselineHalfLife;
|
|
40
|
+
private readonly activityHalfLife;
|
|
41
|
+
private readonly recentRttWindow;
|
|
42
|
+
private readonly minGroupSamples;
|
|
43
|
+
private readonly clock;
|
|
44
|
+
private readonly registry;
|
|
45
|
+
private readonly congestionSignalGauge;
|
|
46
|
+
private readonly warmedGroupsCountGauge;
|
|
47
|
+
constructor(options?: {
|
|
48
|
+
initialLimit?: number;
|
|
49
|
+
minLimit?: number;
|
|
50
|
+
maxLimit?: number;
|
|
51
|
+
maxGroups?: number;
|
|
52
|
+
alpha?: number;
|
|
53
|
+
beta?: number;
|
|
54
|
+
decrease?: (limit: number, didDrop: boolean) => number;
|
|
55
|
+
baselineHalfLife?: number;
|
|
56
|
+
activityHalfLife?: number;
|
|
57
|
+
recentRttWindow?: number;
|
|
58
|
+
minGroupSamples?: number;
|
|
59
|
+
clock?: () => number;
|
|
60
|
+
metricRegistry?: MetricRegistry;
|
|
61
|
+
});
|
|
62
|
+
addSample(_startTime: number, rtt: number, inflight: number, didDrop: boolean, operationName?: string): void;
|
|
63
|
+
get currentLimit(): number;
|
|
64
|
+
subscribe(consumer: (newLimit: number) => void, options?: {
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
}): () => void;
|
|
67
|
+
private computeCongestionSignal;
|
|
68
|
+
private clamp;
|
|
69
|
+
private applyNewLimit;
|
|
70
|
+
toString(): string;
|
|
71
|
+
}
|
|
72
|
+
declare global {
|
|
73
|
+
interface Math {
|
|
74
|
+
sumPrecise?: (numbers: Iterable<number>) => number;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=GroupAwareLimit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupAwareLimit.d.ts","sourceRoot":"","sources":["../../src/limit/GroupAwareLimit.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAS,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAKlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA6DzD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,eAAgB,YAAW,aAAa;IACnD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA0C;IACzE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkD;IAEzE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8C;IACvE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAQ;IAC9C,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAQ;gBAEnC,OAAO,CAAC,EAAE;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;QACvD,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,cAAc,CAAC;KACjC;IAuBD,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,aAAa,CAAC,EAAE,MAAM,GACrB,IAAI;IAkDP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,OAAO,CAAC,uBAAuB;IA8C/B,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,aAAa;IAOrB,QAAQ,IAAI,MAAM;CAGnB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,IAAI;QACZ,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC;KACpD;CACF"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import lruPkg from "lru_map";
|
|
2
|
+
const { LRUMap } = lruPkg;
|
|
3
|
+
import { ListenerSet } from "../ListenerSet.js";
|
|
4
|
+
import { MetricIds, NoopMetricRegistry } from "../MetricRegistry.js";
|
|
5
|
+
import { DecayingHistogram } from "../statistics/DecayingHistogram.js";
|
|
6
|
+
import { ExpMovingAverage } from "../statistics/ExpMovingAverage.js";
|
|
7
|
+
import { log10Scale } from "../utils/index.js";
|
|
8
|
+
function defaultDecrease(limit, didDrop) {
|
|
9
|
+
if (didDrop) {
|
|
10
|
+
return Math.floor(limit * 0.9);
|
|
11
|
+
}
|
|
12
|
+
return limit - log10Scale(Math.floor(limit));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Per-group tracking state. Fixed memory regardless of sample volume.
|
|
16
|
+
*/
|
|
17
|
+
class GroupState {
|
|
18
|
+
/** Long-lived decaying histogram for the p10 baseline. */
|
|
19
|
+
histogram;
|
|
20
|
+
/** Short EMA tracking the group's current RTT. */
|
|
21
|
+
recentRtt;
|
|
22
|
+
/**
|
|
23
|
+
* Decaying counter of recent samples, used for weight and warmup.
|
|
24
|
+
* Decays with its own (shorter) half-life.
|
|
25
|
+
*/
|
|
26
|
+
_activityCount = 0;
|
|
27
|
+
_lastActivityDecayTime;
|
|
28
|
+
activityLambda;
|
|
29
|
+
constructor(baselineHalfLife, activityHalfLife, recentRttWindow) {
|
|
30
|
+
this.histogram = new DecayingHistogram({ halfLife: baselineHalfLife });
|
|
31
|
+
this.recentRtt = new ExpMovingAverage(recentRttWindow, 5);
|
|
32
|
+
this.activityLambda = Math.LN2 / activityHalfLife;
|
|
33
|
+
}
|
|
34
|
+
recordSample(rtt, now) {
|
|
35
|
+
this.histogram.addSample(rtt, now);
|
|
36
|
+
this.recentRtt.addSample(rtt);
|
|
37
|
+
this.applyActivityDecay(now);
|
|
38
|
+
this._activityCount += 1;
|
|
39
|
+
}
|
|
40
|
+
activityCount(now) {
|
|
41
|
+
this.applyActivityDecay(now);
|
|
42
|
+
return this._activityCount;
|
|
43
|
+
}
|
|
44
|
+
applyActivityDecay(now) {
|
|
45
|
+
if (this._lastActivityDecayTime === undefined) {
|
|
46
|
+
this._lastActivityDecayTime = now;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const elapsed = now - this._lastActivityDecayTime;
|
|
50
|
+
if (elapsed <= 0)
|
|
51
|
+
return;
|
|
52
|
+
this._activityCount *= Math.exp(-this.activityLambda * elapsed);
|
|
53
|
+
this._lastActivityDecayTime = now;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Mix-agnostic adaptive concurrency limit that detects congestion using
|
|
58
|
+
* per-group RTT ratios weighted by sample density.
|
|
59
|
+
*
|
|
60
|
+
* Each sample is associated with an operation group via `operationName`. Per-group
|
|
61
|
+
* state tracks a long-lived p10 RTT baseline (decaying histogram) and a short
|
|
62
|
+
* EMA of recent RTT. The congestion signal is the weighted average of
|
|
63
|
+
* `recentRtt / p10` across warmed-up groups, where weight is
|
|
64
|
+
* `sqrt(decayedSampleCount)`.
|
|
65
|
+
*
|
|
66
|
+
* The limit increases additively when the congestion signal is low and inflight
|
|
67
|
+
* is near the limit, and decreases via a configurable `decrease` function when
|
|
68
|
+
* the signal is high or a drop occurs. Before any groups are warmed up, the
|
|
69
|
+
* limit only responds to drops.
|
|
70
|
+
*
|
|
71
|
+
* Samples without an `operationName` do not contribute to any group's state
|
|
72
|
+
* (no histogram, EMA, or activity counter update). However, they still
|
|
73
|
+
* participate in limit decisions: drops always trigger a decrease, and
|
|
74
|
+
* non-drop samples can trigger an increase or decrease based on the current
|
|
75
|
+
* congestion signal from warmed-up groups. This means unnamed operations
|
|
76
|
+
* free-ride on the congestion detection provided by named groups without
|
|
77
|
+
* polluting group baselines.
|
|
78
|
+
*
|
|
79
|
+
* This design is immune to operation mix shifts: a transition from fast to slow
|
|
80
|
+
* operations does not cause a spurious RTT spike because each group is measured
|
|
81
|
+
* against its own baseline.
|
|
82
|
+
*/
|
|
83
|
+
export class GroupAwareLimit {
|
|
84
|
+
_limit;
|
|
85
|
+
limitListeners = new ListenerSet();
|
|
86
|
+
groups;
|
|
87
|
+
minLimit;
|
|
88
|
+
maxLimit;
|
|
89
|
+
alpha;
|
|
90
|
+
beta;
|
|
91
|
+
decrease;
|
|
92
|
+
baselineHalfLife;
|
|
93
|
+
activityHalfLife;
|
|
94
|
+
recentRttWindow;
|
|
95
|
+
minGroupSamples;
|
|
96
|
+
clock;
|
|
97
|
+
registry;
|
|
98
|
+
congestionSignalGauge;
|
|
99
|
+
warmedGroupsCountGauge;
|
|
100
|
+
constructor(options) {
|
|
101
|
+
this._limit = options?.initialLimit ?? 20;
|
|
102
|
+
this.groups = new LRUMap(options?.maxGroups ?? 50);
|
|
103
|
+
this.minLimit = options?.minLimit ?? 10;
|
|
104
|
+
this.maxLimit = options?.maxLimit ?? 200;
|
|
105
|
+
this.alpha = options?.alpha ?? 1.1;
|
|
106
|
+
this.beta = options?.beta ?? 1.5;
|
|
107
|
+
this.decrease = options?.decrease ?? defaultDecrease;
|
|
108
|
+
this.baselineHalfLife = options?.baselineHalfLife ?? 600_000;
|
|
109
|
+
this.activityHalfLife = options?.activityHalfLife ?? 30_000;
|
|
110
|
+
this.recentRttWindow = options?.recentRttWindow ?? 100;
|
|
111
|
+
this.minGroupSamples = options?.minGroupSamples ?? 20;
|
|
112
|
+
this.clock = options?.clock ?? (() => performance.now());
|
|
113
|
+
this.registry = options?.metricRegistry ?? NoopMetricRegistry;
|
|
114
|
+
this.congestionSignalGauge = this.registry.gauge(MetricIds.CONGESTION_SIGNAL_NAME);
|
|
115
|
+
this.warmedGroupsCountGauge = this.registry.gauge(MetricIds.WARMED_GROUPS_COUNT_NAME);
|
|
116
|
+
}
|
|
117
|
+
addSample(_startTime, rtt, inflight, didDrop, operationName) {
|
|
118
|
+
const now = this.clock();
|
|
119
|
+
// Drops are handled before group state updates so that dropped RTTs never
|
|
120
|
+
// pollute baselines or recent-RTT tracking. A dropped request's timing
|
|
121
|
+
// reflects when the drop was detected (e.g. a timeout firing), not how
|
|
122
|
+
// long the operation actually takes to complete.
|
|
123
|
+
if (didDrop) {
|
|
124
|
+
this.applyNewLimit(this.clamp(this.decrease(this._limit, true)));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (operationName !== undefined) {
|
|
128
|
+
let group = this.groups.get(operationName);
|
|
129
|
+
if (!group) {
|
|
130
|
+
group = new GroupState(this.baselineHalfLife, this.activityHalfLife, this.recentRttWindow);
|
|
131
|
+
this.groups.set(operationName, group);
|
|
132
|
+
}
|
|
133
|
+
group.recordSample(rtt, now);
|
|
134
|
+
}
|
|
135
|
+
const result = this.computeCongestionSignal(now);
|
|
136
|
+
if (result === undefined) {
|
|
137
|
+
// No warmed-up groups; hold (drop-only mode).
|
|
138
|
+
this.warmedGroupsCountGauge.record(0);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const { warmedGroupInfos, signal } = result;
|
|
142
|
+
this.warmedGroupsCountGauge.record(warmedGroupInfos.length);
|
|
143
|
+
this.congestionSignalGauge.record(signal);
|
|
144
|
+
for (const { groupName: group, ratio } of warmedGroupInfos) {
|
|
145
|
+
this.registry
|
|
146
|
+
.gauge(MetricIds.GROUP_RTT_RATIO_NAME, { group })
|
|
147
|
+
.record(ratio);
|
|
148
|
+
}
|
|
149
|
+
if (signal > this.beta) {
|
|
150
|
+
this.applyNewLimit(this.clamp(this.decrease(this._limit, false)));
|
|
151
|
+
}
|
|
152
|
+
else if (signal < this.alpha && inflight >= this._limit / 2) {
|
|
153
|
+
this.applyNewLimit(this.clamp(this._limit + 1));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
get currentLimit() {
|
|
157
|
+
return this._limit;
|
|
158
|
+
}
|
|
159
|
+
subscribe(consumer, options = {}) {
|
|
160
|
+
return this.limitListeners.subscribe(consumer, options);
|
|
161
|
+
}
|
|
162
|
+
computeCongestionSignal(now) {
|
|
163
|
+
// See https://github.com/rsms/js-lru/pull/42/changes
|
|
164
|
+
const entries = [
|
|
165
|
+
...this.groups.entries(),
|
|
166
|
+
];
|
|
167
|
+
const warmedGroupInfos = entries
|
|
168
|
+
.map(([groupName, groupState]) => {
|
|
169
|
+
const activity = groupState.activityCount(now);
|
|
170
|
+
if (activity < this.minGroupSamples)
|
|
171
|
+
return undefined;
|
|
172
|
+
const p10 = groupState.histogram.percentile(0.1, now);
|
|
173
|
+
if (!Number.isFinite(p10) || p10 <= 0)
|
|
174
|
+
return undefined;
|
|
175
|
+
const recentRtt = groupState.recentRtt.currentValue;
|
|
176
|
+
if (recentRtt <= 0)
|
|
177
|
+
return undefined;
|
|
178
|
+
const ratio = recentRtt / p10;
|
|
179
|
+
const weight = Math.sqrt(activity);
|
|
180
|
+
return { groupName, ratio, weight };
|
|
181
|
+
})
|
|
182
|
+
// Remove non-warmed or otherwise-invalid groups
|
|
183
|
+
.filter((it) => it !== undefined);
|
|
184
|
+
const totalWeight = sum(warmedGroupInfos.map(({ weight }) => weight));
|
|
185
|
+
if (totalWeight <= 0)
|
|
186
|
+
return undefined;
|
|
187
|
+
const weightedRatioSum = sum(warmedGroupInfos.map(({ weight, ratio }) => weight * ratio));
|
|
188
|
+
return {
|
|
189
|
+
signal: weightedRatioSum / totalWeight,
|
|
190
|
+
warmedGroupInfos,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
clamp(limit) {
|
|
194
|
+
return Math.min(this.maxLimit, Math.max(this.minLimit, Math.floor(limit)));
|
|
195
|
+
}
|
|
196
|
+
applyNewLimit(newLimit) {
|
|
197
|
+
if (newLimit !== this._limit) {
|
|
198
|
+
this._limit = newLimit;
|
|
199
|
+
this.limitListeners.notify(newLimit);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
toString() {
|
|
203
|
+
return `GroupAwareLimit [limit=${this._limit}, groups=${this.groups.size}]`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const sum = typeof Math.sumPrecise === "function"
|
|
207
|
+
? Math.sumPrecise.bind(Math)
|
|
208
|
+
: (numbers) => numbers.reduce((acc, curr) => acc + curr, 0);
|