@zakkster/lite-pick 0.6.0 → 0.7.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/Pick.d.ts +30 -3
- package/Pick.js +144 -4
- package/Pool.d.ts +8 -1
- package/Pool.js +24 -5
- package/package.json +2 -2
package/Pick.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zakkster/lite-pick -- TypeScript declarations.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (LeastConn/SED/NQ). The remaining strategy classes
|
|
6
|
-
* WeightedRandom) are added one per session.
|
|
4
|
+
* M7 (0.7.0): substrate seams + RoundRobin + SmoothWRR + P2C + the exact LeastConn family
|
|
5
|
+
* (LeastConn/SED/NQ) + PeakEWMA (latency-aware P2C). The remaining strategy classes
|
|
6
|
+
* (ConsistentHash, BoundedLoad, WeightedRandom) are added one per session.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/** The single source-of-truth version stamp. */
|
|
@@ -160,3 +160,30 @@ export class NqBalancer extends BalancerBase {
|
|
|
160
160
|
/** The first idle eligible node, else the SED minimum, or `PICK_NONE`. O(cap). */
|
|
161
161
|
pick(): number;
|
|
162
162
|
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* PeakEwmaBalancer -- latency-aware power-of-two-choices (M7, Twitter Finagle's peak-EWMA).
|
|
166
|
+
* Draws two distinct eligible endpoints and returns the lower cost = `(inflight + 1) * ewmaAt(now)`;
|
|
167
|
+
* a slow endpoint (high decayed EWMA rtt) is avoided even with a short queue. `inflight` is the
|
|
168
|
+
* caller-owned Uint32Array read LIVE; the EWMA state (`_ewma` / `_stamp`, Float64) is BALANCER-OWNED
|
|
169
|
+
* and written ONLY by `recordRtt` (the warm feedback path). `pick(now)` decays on READ -- never
|
|
170
|
+
* writes -- so it is 0 B/op, as is `recordRtt`. `now` / `sampleNs` are caller-supplied nanoseconds.
|
|
171
|
+
* Cold start seeds the EWMA to 1.0 -> graceful least-connections, never NaN. O(d)=O(1). Fails
|
|
172
|
+
* closed (`PICK_NONE`) when the whole pool is down.
|
|
173
|
+
*/
|
|
174
|
+
export class PeakEwmaBalancer extends BalancerBase {
|
|
175
|
+
/**
|
|
176
|
+
* @param capacity endpoint count (fixed).
|
|
177
|
+
* @param eligible shared view: 1 = pickable, 0 = down (length >= capacity).
|
|
178
|
+
* @param inflight per-endpoint in-flight counts (length >= capacity), caller-owned, read live.
|
|
179
|
+
* @param tauNs the EWMA time-constant / half-life in nanoseconds (finite, > 0).
|
|
180
|
+
* @param seed deterministic PRNG seed (default 0x9e3779b9); reproducible benches.
|
|
181
|
+
*/
|
|
182
|
+
constructor(capacity: number, eligible: Uint8Array, inflight: Uint32Array, tauNs: number, seed?: number);
|
|
183
|
+
/** The decayed EWMA rtt estimate for endpoint `i` at time `now` (ns). Pure read, zero-alloc. */
|
|
184
|
+
ewmaAt(i: number, now: number): number;
|
|
185
|
+
/** Warm feedback path: record an rtt sample (ns) for endpoint `i` at time `now` (ns). 0 B/op. */
|
|
186
|
+
recordRtt(i: number, sampleNs: number, now: number): void;
|
|
187
|
+
/** Pick by latency-aware power-of-two-choices at time `now` (ns), or `PICK_NONE`. O(d)=O(1). */
|
|
188
|
+
pick(now?: number): number;
|
|
189
|
+
}
|
package/Pick.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zakkster/lite-pick -- zero-GC load-balancing SELECTION KERNEL.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* M7 (0.7.0): substrate seams + seven strategies -- RoundRobin, SmoothWRR, P2C, the exact
|
|
5
|
+
* LeastConn family (LeastConn, SED, NQ), and PeakEWMA (latency-aware P2C). This file ships:
|
|
6
6
|
*
|
|
7
7
|
* - VERSION the single source-of-truth version stamp (3-place sync).
|
|
8
8
|
* - PICK_NONE the fail-closed sentinel (-1): "no endpoint", never a dead pick.
|
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
* charges the NEW request's marginal cost. O(cap)/pick, 0 B/op.
|
|
27
27
|
* - NqBalancer never-queue (IPVS `nq`): an IDLE eligible endpoint immediately if one
|
|
28
28
|
* exists, else SED. The worker-pool fit. O(cap)/pick, 0 B/op.
|
|
29
|
+
* - PeakEwmaBalancer latency-aware P2C (Twitter Finagle's peak-EWMA): draws two distinct
|
|
30
|
+
* eligible endpoints and takes the lower cost = (inflight+1) x decayed EWMA(rtt).
|
|
31
|
+
* Decay-on-READ (pick() never writes -> 0 B/op); the balancer OWNS the Float64
|
|
32
|
+
* _ewma/_stamp state and is its SOLE writer via the warm recordRtt() feedback
|
|
33
|
+
* path (also 0 B/op). Caller-supplied nanosecond clock. O(d)=O(1)/pick.
|
|
29
34
|
*
|
|
30
35
|
* The identity (decisions/0001): lite-pick OWNS NO mutable state it can avoid owning.
|
|
31
36
|
* It reads pre-allocated views (eligibility, inflight, weights, scores) that siblings or
|
|
@@ -33,7 +38,7 @@
|
|
|
33
38
|
* counters live OUTSIDE the kernel. The steady-state pick path allocates 0 B/op.
|
|
34
39
|
*
|
|
35
40
|
* Roster (one strategy per session -- see ROADMAP.md): RoundRobin [M1], SmoothWRR [M2],
|
|
36
|
-
* P2C [M3], LeastConn/SED/NQ [M4], PeakEWMA, ConsistentHash, BoundedLoad, WeightedRandom
|
|
41
|
+
* P2C [M3], LeastConn/SED/NQ [M4], PeakEWMA [M7], ConsistentHash, BoundedLoad, WeightedRandom
|
|
37
42
|
* [planned]. The EXACT-O(log n) fewest-in-flight variant is a deferred @zakkster/lite-logn
|
|
38
43
|
* BinaryHeap optional-peer seam (decisions/0006), not this exact-O(cap) scan.
|
|
39
44
|
*
|
|
@@ -46,7 +51,7 @@
|
|
|
46
51
|
*/
|
|
47
52
|
|
|
48
53
|
/** Version stamp. Synced across package.json and llms.txt (three-place rule). */
|
|
49
|
-
export const VERSION = '0.
|
|
54
|
+
export const VERSION = '0.7.0';
|
|
50
55
|
|
|
51
56
|
/**
|
|
52
57
|
* Fail-closed sentinel returned by pick() when no endpoint is eligible.
|
|
@@ -574,3 +579,138 @@ export class NqBalancer extends BalancerBase {
|
|
|
574
579
|
return best; // -1 when every eligible node has weight 0
|
|
575
580
|
}
|
|
576
581
|
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* PeakEwmaBalancer -- latency-aware power-of-two-choices (M7), Twitter Finagle's peak-EWMA.
|
|
585
|
+
*
|
|
586
|
+
* `pick(now)` draws TWO distinct eligible endpoints (the same rejection-sampling machinery as
|
|
587
|
+
* P2cBalancer -- reused verbatim, not re-implemented) and returns the one with the lower COST,
|
|
588
|
+
* where cost(i) = (inflight[i] + 1) x ewmaAt(i, now). It is P2C over a LATENCY signal instead of
|
|
589
|
+
* raw in-flight count: a slow endpoint (high EWMA rtt) is avoided even when its queue is short,
|
|
590
|
+
* so the pool steers around a degraded-but-up node -- the strategy the multi-region FE case wants.
|
|
591
|
+
* O(d) = O(1) per pick.
|
|
592
|
+
*
|
|
593
|
+
* Ownership (ADR 0001, ADR 0009): `inflight` is the CALLER's Uint32Array, read LIVE (the P2C /
|
|
594
|
+
* LeastConn seam). The EWMA state -- `_ewma` (the decayed rtt estimate) and `_stamp` (the ns
|
|
595
|
+
* timestamp of each node's last update), both Float64Array -- is BALANCER-OWNED (the SmoothWRR
|
|
596
|
+
* precedent: a strategy may own algorithm state), and the balancer is its SOLE writer, via the
|
|
597
|
+
* warm `recordRtt()` feedback path. `pick()` NEVER writes: it decays ON READ, so the hot path
|
|
598
|
+
* stays a pure read -> 0 B/op.
|
|
599
|
+
*
|
|
600
|
+
* Decay-on-read: ewmaAt(i, now) = _ewma[i] x exp(-(now - _stamp[i]) / tau). No write, no clock
|
|
601
|
+
* call on the gated path -- `now` (and the rtt sample) are CALLER-supplied nanoseconds, consistent
|
|
602
|
+
* between `pick(now)` and `recordRtt(i, sampleNs, now)`, so the whole strategy is deterministic
|
|
603
|
+
* and testable and allocates nothing.
|
|
604
|
+
*
|
|
605
|
+
* Cold start: `_ewma` seeds to 1.0 and `_stamp` to 0 at construction, so before any sample
|
|
606
|
+
* cost(i) ~ (inflight[i] + 1) x 1 and PeakEWMA degrades GRACEFULLY to plain least-connections
|
|
607
|
+
* (P2C-over-inflight). It is never NaN.
|
|
608
|
+
*
|
|
609
|
+
* Anti-flap (ADR 0002, ADR 0009): the EWMA half-life IS the smoothing -- a single slow sample
|
|
610
|
+
* snaps the cost up instantly and it decays back over ~tau, so there is NO extra dwell/hysteresis.
|
|
611
|
+
*
|
|
612
|
+
* Deferred (ADR 0009 / llms.txt): a p99-aware variant scoring inflight x p99Rtt via a per-node
|
|
613
|
+
* @zakkster/lite-sketch `DDSketch` (optional peer, 0 B/op `add`). EWMA-mean is the shipped,
|
|
614
|
+
* zero-peer default; `peerDependencies` stays empty until a shipped path imports the sketch.
|
|
615
|
+
*
|
|
616
|
+
* Bound: O(d) = O(1) per pick (two expected-O(1) rejection draws + two exp() + a compare),
|
|
617
|
+
* 0 B/op on BOTH `pick()` and `recordRtt()` (torture + PerfGate). Fails closed (PICK_NONE) when
|
|
618
|
+
* the whole pool is down.
|
|
619
|
+
*/
|
|
620
|
+
export class PeakEwmaBalancer extends BalancerBase {
|
|
621
|
+
/**
|
|
622
|
+
* @param {number} capacity endpoint count (fixed).
|
|
623
|
+
* @param {Uint8Array} eligible shared view: 1 = pickable, 0 = down (length >= capacity).
|
|
624
|
+
* @param {Uint32Array} inflight per-endpoint in-flight counts (length >= capacity),
|
|
625
|
+
* caller-owned and only READ here.
|
|
626
|
+
* @param {number} tauNs the EWMA time-constant / half-life in nanoseconds (> 0, finite):
|
|
627
|
+
* larger tau = slower decay = longer memory of a latency spike.
|
|
628
|
+
* @param {number} [seed=0x9e3779b9] deterministic PRNG seed (reproducible benches).
|
|
629
|
+
*/
|
|
630
|
+
constructor(capacity, eligible, inflight, tauNs, seed = 0x9e3779b9) {
|
|
631
|
+
super(capacity, eligible);
|
|
632
|
+
// Validate typeof-first, BEFORE allocating the owned Float64 state (fail closed early).
|
|
633
|
+
if (!(inflight instanceof Uint32Array) || inflight.length < capacity) {
|
|
634
|
+
throw new RangeError('[lite-pick] inflight must be a Uint32Array of length >= capacity');
|
|
635
|
+
}
|
|
636
|
+
if (typeof tauNs !== 'number') {
|
|
637
|
+
throw new TypeError('[lite-pick] tauNs must be a number');
|
|
638
|
+
}
|
|
639
|
+
if (!Number.isFinite(tauNs) || tauNs <= 0) {
|
|
640
|
+
throw new RangeError('[lite-pick] tauNs must be a finite number > 0');
|
|
641
|
+
}
|
|
642
|
+
this._inflight = inflight;
|
|
643
|
+
this._tau = tauNs;
|
|
644
|
+
this._rng = new Prng(seed);
|
|
645
|
+
this._ewma = new Float64Array(capacity);
|
|
646
|
+
this._stamp = new Float64Array(capacity); // all-zero: last-update timestamp
|
|
647
|
+
for (let i = 0; i < capacity; i++) this._ewma[i] = 1.0; // cold start -> graceful LeastConn
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* A uniformly random ELIGIBLE index, or PICK_NONE if none. Reuses P2cBalancer's exact
|
|
652
|
+
* rejection-sampling draw (ADR 0005) verbatim -- same `_rng` / `_eligible` / `_live` fields,
|
|
653
|
+
* no re-implementation, no owned draw-set. Internal, zero-alloc.
|
|
654
|
+
* @returns {number}
|
|
655
|
+
*/
|
|
656
|
+
_draw() {
|
|
657
|
+
return P2cBalancer.prototype._draw.call(this);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* The decayed EWMA rtt estimate for endpoint i at time `now` (ns). Pure READ -- exponential
|
|
662
|
+
* decay applied on read, never written. Cold (unsampled) nodes read ~1.0. Zero-alloc.
|
|
663
|
+
* @param {number} i
|
|
664
|
+
* @param {number} now caller-supplied nanoseconds
|
|
665
|
+
* @returns {number}
|
|
666
|
+
*/
|
|
667
|
+
ewmaAt(i, now) {
|
|
668
|
+
return this._ewma[i] * Math.exp(-(now - this._stamp[i]) / this._tau);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Record an rtt SAMPLE for endpoint i at time `now` (the warm feedback path -- NOT the hot
|
|
673
|
+
* pick path). The Finagle peak rule: decay the stored estimate to `now`, then SNAP UP to the
|
|
674
|
+
* sample if it is larger (a spike is felt instantly) else ease toward it (it decays back over
|
|
675
|
+
* ~tau). The balancer is the SOLE writer of `_ewma` / `_stamp`. Zero-alloc on the success path.
|
|
676
|
+
* @param {number} i endpoint index
|
|
677
|
+
* @param {number} sampleNs observed rtt in nanoseconds (finite, >= 0)
|
|
678
|
+
* @param {number} now caller-supplied nanoseconds (finite), consistent with pick(now)
|
|
679
|
+
*/
|
|
680
|
+
recordRtt(i, sampleNs, now) {
|
|
681
|
+
if (typeof i !== 'number' || typeof sampleNs !== 'number' || typeof now !== 'number') {
|
|
682
|
+
throw new TypeError('[lite-pick] recordRtt(i, sampleNs, now) requires numbers');
|
|
683
|
+
}
|
|
684
|
+
if (i < 0 || i >= this._cap) throw new RangeError('[lite-pick] index out of range: ' + i);
|
|
685
|
+
if (!Number.isFinite(sampleNs) || sampleNs < 0) {
|
|
686
|
+
throw new RangeError('[lite-pick] sampleNs must be a finite number >= 0');
|
|
687
|
+
}
|
|
688
|
+
if (!Number.isFinite(now)) throw new RangeError('[lite-pick] now must be a finite number');
|
|
689
|
+
const w = Math.exp(-(now - this._stamp[i]) / this._tau);
|
|
690
|
+
const e = this._ewma[i] * w;
|
|
691
|
+
this._ewma[i] = sampleNs > e ? sampleNs : e + (sampleNs - e) * (1 - w);
|
|
692
|
+
this._stamp[i] = now;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Pick by latency-aware power-of-two-choices: two distinct eligible draws, lower cost =
|
|
697
|
+
* (inflight+1) x ewmaAt(now) wins; a tie goes to the first draw. PICK_NONE (fail closed) iff
|
|
698
|
+
* the whole pool is down. O(d)=O(1), 0 B/op (pure read -- no write, no clock call).
|
|
699
|
+
* @param {number} now caller-supplied nanoseconds (consistent with recordRtt)
|
|
700
|
+
* @returns {number}
|
|
701
|
+
*/
|
|
702
|
+
pick(now) {
|
|
703
|
+
const a = this._draw();
|
|
704
|
+
if (a < 0) return PICK_NONE; // whole pool down: fail closed
|
|
705
|
+
if (this._live === 1) return a; // only one eligible: it is both choices
|
|
706
|
+
// A DISTINCT second draw, bounded (the ADR 0005 rationale): at live>=2 each redraw misses
|
|
707
|
+
// with probability <= 1/2, so 32 tries leaves a ~2^-32 collision chance, expected-O(1), 0 B/op.
|
|
708
|
+
let b = this._draw();
|
|
709
|
+
for (let t = 0; b === a && t < 32; t++) b = this._draw();
|
|
710
|
+
if (b < 0 || b === a) return a; // astronomically rare: fall back to the first draw
|
|
711
|
+
const inf = this._inflight, ewma = this._ewma, stamp = this._stamp, tau = this._tau;
|
|
712
|
+
const costA = (inf[a] + 1) * (ewma[a] * Math.exp(-(now - stamp[a]) / tau));
|
|
713
|
+
const costB = (inf[b] + 1) * (ewma[b] * Math.exp(-(now - stamp[b]) / tau));
|
|
714
|
+
return costB < costA ? b : a; // lower cost wins; tie -> the first draw
|
|
715
|
+
}
|
|
716
|
+
}
|
package/Pool.d.ts
CHANGED
|
@@ -10,9 +10,11 @@ export const VERSION: string;
|
|
|
10
10
|
|
|
11
11
|
/** The minimal balancer shape Pool drives (any lite-pick strategy satisfies it). */
|
|
12
12
|
export interface Balancer {
|
|
13
|
-
pick(): number;
|
|
13
|
+
pick(now?: number): number;
|
|
14
14
|
readonly capacity: number;
|
|
15
15
|
readonly live: number;
|
|
16
|
+
/** Optional latency-feedback sink (PeakEwmaBalancer); fed on settle when a clock is supplied. */
|
|
17
|
+
recordRtt?(i: number, sampleNs: number, now: number): void;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/** Options for `Pool.run`. */
|
|
@@ -21,6 +23,11 @@ export interface RunOptions {
|
|
|
21
23
|
signal?: AbortSignal;
|
|
22
24
|
/** Max distinct-endpoint attempts (default 1 = no failover). */
|
|
23
25
|
tries?: number;
|
|
26
|
+
/**
|
|
27
|
+
* A caller-owned nanosecond clock. When present it drives `pick(now)` and the opt-in
|
|
28
|
+
* `recordRtt` latency feedback for a latency-aware balancer (PeakEwma); otherwise inert.
|
|
29
|
+
*/
|
|
30
|
+
clock?: () => number;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
/**
|
package/Pool.js
CHANGED
|
@@ -70,11 +70,20 @@ export class Pool {
|
|
|
70
70
|
* DIFFERENT endpoint -- up to `tries` attempts, then throw the last error. All counts this run
|
|
71
71
|
* raised are released before returning or throwing (net-zero per run).
|
|
72
72
|
*
|
|
73
|
+
* A LATENCY-AWARE balancer (PeakEwmaBalancer -- anything duck-typing `recordRtt`) is fed on
|
|
74
|
+
* settle WHEN a `clock` is supplied: Pool reads `clock()` (caller-owned nanoseconds) before the
|
|
75
|
+
* attempt, passes it to `pick(now)`, and on a SUCCESSFUL settle records `recordRtt(i, elapsed,
|
|
76
|
+
* now2)`. Without a `clock`, or against a balancer with no `recordRtt`, the hook is INERT --
|
|
77
|
+
* Pool stays generic, the in-flight counter stays net-zero, and abort/failover are unaffected.
|
|
78
|
+
* The kernel `pick()` remains 0 B/op; this wrapper is not held to that bar.
|
|
79
|
+
*
|
|
73
80
|
* @template T
|
|
74
81
|
* @param {(endpoint: number, signal?: AbortSignal) => (Promise<T>|T)} fn the per-endpoint work.
|
|
75
|
-
* @param {{ signal?: AbortSignal, tries?: number }} [opts] `tries`
|
|
76
|
-
* is the max number of distinct-endpoint attempts; `signal` is
|
|
77
|
-
* already aborted after a failure, stops failover (the abort
|
|
82
|
+
* @param {{ signal?: AbortSignal, tries?: number, clock?: () => number }} [opts] `tries`
|
|
83
|
+
* (default 1 = no failover) is the max number of distinct-endpoint attempts; `signal` is
|
|
84
|
+
* passed to `fn` and, when already aborted after a failure, stops failover (the abort
|
|
85
|
+
* propagates, no re-pick); `clock` is a caller-owned nanosecond source that, when present,
|
|
86
|
+
* drives `pick(now)` and the opt-in `recordRtt` latency feedback for a latency-aware balancer.
|
|
78
87
|
* @returns {Promise<T>}
|
|
79
88
|
*/
|
|
80
89
|
async run(fn, opts) {
|
|
@@ -82,12 +91,17 @@ export class Pool {
|
|
|
82
91
|
const rawTries = opts && opts.tries != null ? (opts.tries | 0) : 1;
|
|
83
92
|
const tries = rawTries > 0 ? rawTries : 1;
|
|
84
93
|
const signal = opts ? opts.signal : undefined;
|
|
94
|
+
const clock = opts && typeof opts.clock === 'function' ? opts.clock : undefined;
|
|
85
95
|
const inflight = this._inflight, b = this._b;
|
|
96
|
+
// Opt-in latency feedback: only when BOTH a clock is supplied AND the balancer duck-types
|
|
97
|
+
// recordRtt. Otherwise inert -- Pool stays generic and byte-for-byte behaviour is unchanged.
|
|
98
|
+
const rtt = clock !== undefined && typeof b.recordRtt === 'function';
|
|
86
99
|
const held = []; // endpoints incremented this run (kept elevated across failover)
|
|
87
100
|
let lastErr;
|
|
88
101
|
try {
|
|
89
102
|
for (let attempt = 0; attempt < tries; attempt++) {
|
|
90
|
-
const
|
|
103
|
+
const now = clock !== undefined ? clock() : undefined;
|
|
104
|
+
const i = b.pick(now);
|
|
91
105
|
if (i === PICK_NONE) {
|
|
92
106
|
if (attempt === 0) {
|
|
93
107
|
const e = new Error('[lite-pick] no eligible endpoint');
|
|
@@ -99,7 +113,12 @@ export class Pool {
|
|
|
99
113
|
inflight[i] = (inflight[i] + 1) >>> 0;
|
|
100
114
|
held.push(i);
|
|
101
115
|
try {
|
|
102
|
-
|
|
116
|
+
const out = await fn(i, signal);
|
|
117
|
+
if (rtt) { // successful settle: feed the measured rtt back to the balancer
|
|
118
|
+
const done = clock();
|
|
119
|
+
b.recordRtt(i, done > now ? done - now : 0, done);
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
103
122
|
} catch (err) {
|
|
104
123
|
lastErr = err;
|
|
105
124
|
if (signal && signal.aborted) throw err; // abort: stop failover, propagate
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zakkster/lite-pick",
|
|
3
3
|
"author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": "Zero-dependency, zero-GC load-balancing selection kernel: one hot pick() -> endpoint index over a fixed pool, 0 B/op steady-state. A pure selector (consumes health/circuit state, never a proxy) for the in-process hop, complementary to AWS NLB/ALB. Tree-shakeable ESM roster: RoundRobin, SmoothWRR, P2C, LeastConn, SED, NQ,
|
|
4
|
+
"version": "0.7.0",
|
|
5
|
+
"description": "Zero-dependency, zero-GC load-balancing selection kernel: one hot pick() -> endpoint index over a fixed pool, 0 B/op steady-state. A pure selector (consumes health/circuit state, never a proxy) for the in-process hop, complementary to AWS NLB/ALB. Tree-shakeable ESM roster: RoundRobin, SmoothWRR, P2C, LeastConn, SED, NQ, PeakEWMA (latency-aware peak-EWMA), plus consistent hashing; the /pool subpath adds dispatch/settle counters + failover and a duck-typed query-cache fetcher.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./Pick.js",
|
|
8
8
|
"module": "./Pick.js",
|