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.
Files changed (47) hide show
  1. package/dist/Limiter.d.ts +7 -0
  2. package/dist/Limiter.d.ts.map +1 -1
  3. package/dist/Limiter.js +5 -2
  4. package/dist/MetricRegistry.d.ts +3 -0
  5. package/dist/MetricRegistry.d.ts.map +1 -1
  6. package/dist/MetricRegistry.js +3 -0
  7. package/dist/index.d.ts +3 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +3 -2
  10. package/dist/limit/AIMDLimit.d.ts +12 -1
  11. package/dist/limit/AIMDLimit.d.ts.map +1 -1
  12. package/dist/limit/AIMDLimit.js +12 -3
  13. package/dist/limit/FixedLimit.d.ts +1 -1
  14. package/dist/limit/FixedLimit.d.ts.map +1 -1
  15. package/dist/limit/FixedLimit.js +1 -1
  16. package/dist/limit/GradientLimit.d.ts +1 -1
  17. package/dist/limit/GradientLimit.d.ts.map +1 -1
  18. package/dist/limit/GradientLimit.js +1 -1
  19. package/dist/limit/GroupAwareLimit.d.ts +77 -0
  20. package/dist/limit/GroupAwareLimit.d.ts.map +1 -0
  21. package/dist/limit/GroupAwareLimit.js +208 -0
  22. package/dist/limit/OperationGroupedLimit.d.ts +78 -0
  23. package/dist/limit/OperationGroupedLimit.d.ts.map +1 -0
  24. package/dist/limit/OperationGroupedLimit.js +93 -0
  25. package/dist/limit/SettableLimit.d.ts +1 -1
  26. package/dist/limit/SettableLimit.d.ts.map +1 -1
  27. package/dist/limit/SettableLimit.js +1 -1
  28. package/dist/limit/StreamingLimit.d.ts +3 -1
  29. package/dist/limit/StreamingLimit.d.ts.map +1 -1
  30. package/dist/limit/TracingLimitDecorator.d.ts +1 -1
  31. package/dist/limit/TracingLimitDecorator.d.ts.map +1 -1
  32. package/dist/limit/TracingLimitDecorator.js +2 -2
  33. package/dist/limit/VegasLimit.d.ts +5 -4
  34. package/dist/limit/VegasLimit.d.ts.map +1 -1
  35. package/dist/limit/VegasLimit.js +12 -17
  36. package/dist/limit/WindowedLimit.d.ts +8 -1
  37. package/dist/limit/WindowedLimit.d.ts.map +1 -1
  38. package/dist/limit/WindowedLimit.js +16 -4
  39. package/dist/limit/window/PercentileSampleWindow.d.ts.map +1 -1
  40. package/dist/limit/window/PercentileSampleWindow.js +40 -4
  41. package/dist/statistics/DecayingHistogram.d.ts +63 -0
  42. package/dist/statistics/DecayingHistogram.d.ts.map +1 -0
  43. package/dist/statistics/DecayingHistogram.js +144 -0
  44. package/dist/utils/index.d.ts +6 -0
  45. package/dist/utils/index.d.ts.map +1 -1
  46. package/dist/utils/index.js +13 -0
  47. 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;
@@ -1 +1 @@
1
- {"version":3,"file":"Limiter.d.ts","sourceRoot":"","sources":["../src/Limiter.ts"],"names":[],"mappings":"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"}
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 {
@@ -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;;;;;;;;;;CAUZ,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"}
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"}
@@ -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";
@@ -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,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,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,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,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"}
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;CAClB;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;gBAEtB,OAAO,GAAE,gBAAqB;IAiB1C,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAapF,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"}
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"}
@@ -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
- currentLimit = Math.floor(currentLimit * this.backoffRatio);
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 * 2 >= currentLimit) {
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,GAChB,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"}
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"}
@@ -7,7 +7,7 @@ export class FixedLimit {
7
7
  constructor(limit) {
8
8
  this._limit = limit;
9
9
  }
10
- addSample(_startTime, _rtt, _inflight, _didDrop) {
10
+ addSample(_startTime, _rtt, _inflight, _didDrop, _operationName) {
11
11
  }
12
12
  get currentLimit() {
13
13
  return this._limit;
@@ -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,GAChB,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"}
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);