pond-ts 0.14.2 → 0.14.3
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/CHANGELOG.md +75 -14
- package/dist/reducers/samples.d.ts.map +1 -1
- package/dist/reducers/samples.js +25 -3
- package/dist/reducers/samples.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,10 +7,72 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
7
7
|
file covers both packages. Pre-1.0: minor bumps may include new features and
|
|
8
8
|
type-level changes; patch bumps are strictly additive.
|
|
9
9
|
|
|
10
|
-
[Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.14.
|
|
10
|
+
[Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.14.3...HEAD
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [0.14.3] — 2026-05-04
|
|
15
|
+
|
|
16
|
+
A targeted allocation fix in the `'samples'` reducer's rolling-state
|
|
17
|
+
implementation. Motivated by gRPC experiment V7 numbers — at the
|
|
18
|
+
ceiling regime (1k partitions × 1k events/s, 1M target) the all-
|
|
19
|
+
pond pipeline using `samples()` regressed throughput ~19% vs V6's
|
|
20
|
+
hybrid pond-rolling + manual-deque pattern, with +17% heap at
|
|
21
|
+
moderate loads. Per-event cost analysis pointed at a 1-element
|
|
22
|
+
`ScalarValue[]` allocation per scalar `add()` — one wasted
|
|
23
|
+
allocation per event compounding under sustained kHz × N-partition
|
|
24
|
+
load.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **`samples.rollingState()` skips array wrap for scalar source
|
|
29
|
+
columns.** Scalar values (the common case at saturation) now
|
|
30
|
+
store directly into the keyed map; only array-kind sources
|
|
31
|
+
build a sub-array (because `remove(index)` needs to drop a
|
|
32
|
+
single event's contributions together). Snapshot branches on
|
|
33
|
+
`Array.isArray` to flatten the mixed map.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Focused micro-bench (5M scalar add+remove cycles):
|
|
37
|
+
median (ms) min (ms) max (ms)
|
|
38
|
+
baseline (v0.14.2) 239.85 236.62 244.58
|
|
39
|
+
v0.14.3 209.09 207.42 215.26
|
|
40
|
+
delta −12.8% −12.3% −12.0%
|
|
41
|
+
|
|
42
|
+
Integration bench (100k events × N hosts, full pipeline):
|
|
43
|
+
Tight wall-clock parity within run-to-run noise across all
|
|
44
|
+
scenarios (samples 1m/5s, scalar/array). Allocation pressure
|
|
45
|
+
isn't the dominant cost at this scale; the optimization
|
|
46
|
+
compounds only at saturation regimes where GC pressure stacks.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Behavior is preserved bit-for-bit — every existing
|
|
50
|
+
`samples-reducer.test.ts` assertion passes without modification.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- `packages/core/scripts/perf-samples-reducer.mjs` — benchmark
|
|
55
|
+
covering the focused micro-bench + four integration scenarios
|
|
56
|
+
(scalar moderate / scalar high-cardinality / scalar high-churn
|
|
57
|
+
/ array source) with a comparison anchor against `'avg'` on
|
|
58
|
+
the same shape. Run with `node --expose-gc` for heap numbers.
|
|
59
|
+
|
|
60
|
+
### Note on saturation regimes
|
|
61
|
+
|
|
62
|
+
V7's regression isn't fully closed by this fix. The remaining gap
|
|
63
|
+
is architectural — V7 routes events through two full
|
|
64
|
+
`LiveRollingAggregation` pipelines (Map ops + reducer state +
|
|
65
|
+
trigger dispatch + subscriber fan-out per pipeline), where V6's
|
|
66
|
+
hybrid had one pond rolling for stats plus a passive
|
|
67
|
+
`array.push` listener for raw values. At the kHz × 1k-partition
|
|
68
|
+
saturation regime, the manual-deque pattern is genuinely the
|
|
69
|
+
right shape; pond's `samples` is for typical loads where per-
|
|
70
|
+
event overhead is invisible. A shared-buffer primitive (parked
|
|
71
|
+
as `tap()` in PLAN.md) would close the saturation gap; out of
|
|
72
|
+
scope for v0.14.3.
|
|
73
|
+
|
|
74
|
+
[0.14.3]: https://github.com/pjm17971/pond-ts/compare/v0.14.2...v0.14.3
|
|
75
|
+
|
|
14
76
|
## [0.14.2] — 2026-05-03
|
|
15
77
|
|
|
16
78
|
Hotfix over v0.14.1 — closes a type-narrowing gap on the new
|
|
@@ -91,7 +153,7 @@ either). Two related changes ship together to close both gaps.
|
|
|
91
153
|
'1m',
|
|
92
154
|
{
|
|
93
155
|
mean: { from: 'cpu', using: 'avg' },
|
|
94
|
-
sd:
|
|
156
|
+
sd: { from: 'cpu', using: 'stdev' },
|
|
95
157
|
},
|
|
96
158
|
{ trigger: Trigger.every('30s') },
|
|
97
159
|
);
|
|
@@ -121,7 +183,6 @@ either). Two related changes ship together to close both gaps.
|
|
|
121
183
|
function-typed reducers. New `bucketStateFor` and `rollingStateFor`
|
|
122
184
|
helpers in `reducers/index.ts` route built-ins to their dedicated
|
|
123
185
|
O(1) machinery and wrap custom functions in a generic adapter:
|
|
124
|
-
|
|
125
186
|
- **Bucket adapter** (`LiveAggregation`): buffers values, calls
|
|
126
187
|
the function once at `snapshot()` time. O(N) per snapshot.
|
|
127
188
|
- **Rolling adapter** (`LiveRollingAggregation`,
|
|
@@ -140,7 +201,7 @@ either). Two related changes ship together to close both gaps.
|
|
|
140
201
|
|
|
141
202
|
Pre-v0.14.1, calling `live.rolling(...)` with a custom-function
|
|
142
203
|
reducer threw `TypeError: live rolling reducer for output 'X' must
|
|
143
|
-
|
|
204
|
+
be a built-in name; ...`. Post-v0.14.1, the same call constructs
|
|
144
205
|
successfully and runs.
|
|
145
206
|
|
|
146
207
|
### Tests
|
|
@@ -180,13 +241,13 @@ re-allocations). Both root-caused, both fixed.
|
|
|
180
241
|
Benchmark deltas on `scripts/perf-live-partitioned.mjs`
|
|
181
242
|
(100k events, median ms):
|
|
182
243
|
|
|
183
|
-
| Scenario
|
|
184
|
-
|
|
|
185
|
-
| bare `LiveSeries.push`
|
|
186
|
-
| `partitionBy('host')` routing (10)
|
|
187
|
-
| `partitionBy + collect()`
|
|
188
|
-
| `partitionBy + apply(fill)`
|
|
189
|
-
| `partitionBy('host')` routing (1000)
|
|
244
|
+
| Scenario | Before | After | Δ |
|
|
245
|
+
| ------------------------------------ | -----: | ----: | -------: |
|
|
246
|
+
| bare `LiveSeries.push` | 41.11 | 30.08 | **−27%** |
|
|
247
|
+
| `partitionBy('host')` routing (10) | 83.14 | 39.10 | **−53%** |
|
|
248
|
+
| `partitionBy + collect()` | 124.82 | 49.96 | **−60%** |
|
|
249
|
+
| `partitionBy + apply(fill)` | 120.53 | 49.64 | **−59%** |
|
|
250
|
+
| `partitionBy('host')` routing (1000) | 105.92 | 43.23 | **−59%** |
|
|
190
251
|
|
|
191
252
|
The bare-push delta is from the byte-estimate removal; the
|
|
192
253
|
partition-routing deltas are from the trusted-pipeline path that
|
|
@@ -429,7 +490,7 @@ the live surface.
|
|
|
429
490
|
- **Better error message when a custom-function reducer is passed to
|
|
430
491
|
live aggregation.** `LiveAggregation` already failed at construction
|
|
431
492
|
via `resolveReducer(reducer)` (with a generic `unsupported aggregate
|
|
432
|
-
|
|
493
|
+
reducer` message); now the eager built-in-name check runs first and
|
|
433
494
|
emits a targeted error pointing at the `AggregateOutputMap` alias
|
|
434
495
|
workaround. Same eager behavior on `LivePartitionedSyncRolling`,
|
|
435
496
|
which previously failed lazily when the first partition spawned —
|
|
@@ -463,8 +524,8 @@ the live surface.
|
|
|
463
524
|
required TS to see the `trigger` field's discriminator at the call
|
|
464
525
|
site — so a caller writing
|
|
465
526
|
`const opts: LiveRollingOptions = { trigger: Trigger.event() };
|
|
466
|
-
|
|
467
|
-
|
|
527
|
+
partitioned.rolling(window, mapping, opts);` got `TS2769 No
|
|
528
|
+
overload matches this call`. Pre-existing hole on the partitioned
|
|
468
529
|
surface; surfaced by the v0.13.0 Codex adversarial pass. Closed by
|
|
469
530
|
adding catch-all overloads that accept the broader
|
|
470
531
|
`LiveRollingOptions` and return the union of both trigger
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"samples.d.ts","sourceRoot":"","sources":["../../src/reducers/samples.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAoB7C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"samples.d.ts","sourceRoot":"","sources":["../../src/reducers/samples.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAoB7C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,OAAO,EAAE,UA2DrB,CAAC"}
|
package/dist/reducers/samples.js
CHANGED
|
@@ -55,11 +55,28 @@ export const samples = {
|
|
|
55
55
|
rollingState() {
|
|
56
56
|
// Map keyed by event index so `remove` is O(1). `Array.from(map.values())`
|
|
57
57
|
// returns insertion order, which matches arrival order.
|
|
58
|
+
//
|
|
59
|
+
// Stored value is `ScalarValue | ScalarValue[]`: scalar source columns
|
|
60
|
+
// (the common case at kHz × N-partition load) skip wrapping each event
|
|
61
|
+
// in a 1-element array, dropping one allocation per `add`. Only
|
|
62
|
+
// array-kind sources need a sub-array (so a single event's contributions
|
|
63
|
+
// can be dropped together on `remove(index)`).
|
|
58
64
|
const items = new Map();
|
|
59
65
|
return {
|
|
60
66
|
add(index, v) {
|
|
67
|
+
if (v === undefined)
|
|
68
|
+
return;
|
|
69
|
+
if (isScalar(v)) {
|
|
70
|
+
items.set(index, v);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Array-kind source: flatten one level, store the sub-array so
|
|
74
|
+
// remove(index) drops every contribution from this event.
|
|
61
75
|
const collected = [];
|
|
62
|
-
|
|
76
|
+
for (const element of v) {
|
|
77
|
+
if (isScalar(element))
|
|
78
|
+
collected.push(element);
|
|
79
|
+
}
|
|
63
80
|
if (collected.length > 0)
|
|
64
81
|
items.set(index, collected);
|
|
65
82
|
},
|
|
@@ -68,9 +85,14 @@ export const samples = {
|
|
|
68
85
|
},
|
|
69
86
|
snapshot() {
|
|
70
87
|
const out = [];
|
|
71
|
-
for (const
|
|
72
|
-
|
|
88
|
+
for (const v of items.values()) {
|
|
89
|
+
if (Array.isArray(v)) {
|
|
90
|
+
for (const x of v)
|
|
91
|
+
out.push(x);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
73
94
|
out.push(v);
|
|
95
|
+
}
|
|
74
96
|
}
|
|
75
97
|
return out;
|
|
76
98
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"samples.js","sourceRoot":"","sources":["../../src/reducers/samples.ts"],"names":[],"mappings":"AAGA,SAAS,QAAQ,CAAC,CAA0B;IAC1C,OAAO,CACL,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS,CACzE,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,SAAS,WAAW,CAAC,GAAkB,EAAE,CAA0B;IACjE,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO;IAC5B,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAChB,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACZ,OAAO;IACT,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,CAAC,EAAE,CAAC;QACxB,IAAI,QAAQ,CAAC,OAAO,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,OAAO,GAAe;IACjC,UAAU,EAAE,OAAO;IACnB,MAAM,CAAC,OAAO;QACZ,MAAM,GAAG,GAAkB,EAAE,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC7C,OAAO,GAAG,CAAC;IACb,CAAC;IACD,WAAW;QACT,MAAM,KAAK,GAAkB,EAAE,CAAC;QAChC,OAAO;YACL,GAAG,CAAC,CAAC;gBACH,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACxB,CAAC;YACD,QAAQ;gBACN,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC;YACvB,CAAC;SACF,CAAC;IACJ,CAAC;IACD,YAAY;QACV,2EAA2E;QAC3E,wDAAwD;QACxD,MAAM,KAAK,GAAG,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"samples.js","sourceRoot":"","sources":["../../src/reducers/samples.ts"],"names":[],"mappings":"AAGA,SAAS,QAAQ,CAAC,CAA0B;IAC1C,OAAO,CACL,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS,CACzE,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,SAAS,WAAW,CAAC,GAAkB,EAAE,CAA0B;IACjE,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO;IAC5B,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAChB,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACZ,OAAO;IACT,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,CAAC,EAAE,CAAC;QACxB,IAAI,QAAQ,CAAC,OAAO,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,OAAO,GAAe;IACjC,UAAU,EAAE,OAAO;IACnB,MAAM,CAAC,OAAO;QACZ,MAAM,GAAG,GAAkB,EAAE,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC7C,OAAO,GAAG,CAAC;IACb,CAAC;IACD,WAAW;QACT,MAAM,KAAK,GAAkB,EAAE,CAAC;QAChC,OAAO;YACL,GAAG,CAAC,CAAC;gBACH,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACxB,CAAC;YACD,QAAQ;gBACN,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC;YACvB,CAAC;SACF,CAAC;IACJ,CAAC;IACD,YAAY;QACV,2EAA2E;QAC3E,wDAAwD;QACxD,EAAE;QACF,uEAAuE;QACvE,uEAAuE;QACvE,gEAAgE;QAChE,yEAAyE;QACzE,+CAA+C;QAC/C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAuC,CAAC;QAC7D,OAAO;YACL,GAAG,CAAC,KAAK,EAAE,CAAC;gBACV,IAAI,CAAC,KAAK,SAAS;oBAAE,OAAO;gBAC5B,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;oBAChB,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;oBACpB,OAAO;gBACT,CAAC;gBACD,+DAA+D;gBAC/D,0DAA0D;gBAC1D,MAAM,SAAS,GAAkB,EAAE,CAAC;gBACpC,KAAK,MAAM,OAAO,IAAI,CAAC,EAAE,CAAC;oBACxB,IAAI,QAAQ,CAAC,OAAO,CAAC;wBAAE,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACjD,CAAC;gBACD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;oBAAE,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YACxD,CAAC;YACD,MAAM,CAAC,KAAK,EAAE,EAAE;gBACd,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;YACD,QAAQ;gBACN,MAAM,GAAG,GAAkB,EAAE,CAAC;gBAC9B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;oBAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;wBACrB,KAAK,MAAM,CAAC,IAAI,CAAC;4BAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;oBACjC,CAAC;yBAAM,CAAC;wBACN,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;oBACd,CAAC;gBACH,CAAC;gBACD,OAAO,GAAG,CAAC;YACb,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
|