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 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.2...HEAD
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: { from: 'cpu', using: 'stdev' },
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
- be a built-in name; ...`. Post-v0.14.1, the same call constructs
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 | Before | After | Δ |
184
- | ------------------------------------- | -----: | -----: | ------: |
185
- | bare `LiveSeries.push` | 41.11 | 30.08 | **−27%** |
186
- | `partitionBy('host')` routing (10) | 83.14 | 39.10 | **−53%** |
187
- | `partitionBy + collect()` | 124.82 | 49.96 | **−60%** |
188
- | `partitionBy + apply(fill)` | 120.53 | 49.64 | **−59%** |
189
- | `partitionBy('host')` routing (1000) | 105.92 | 43.23 | **−59%** |
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
- reducer` message); now the eager built-in-name check runs first and
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
- partitioned.rolling(window, mapping, opts);` got `TS2769 No
467
- overload matches this call`. Pre-existing hole on the partitioned
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,UAwCrB,CAAC"}
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"}
@@ -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
- collectInto(collected, v);
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 arr of items.values()) {
72
- for (const v of arr)
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,EAAyB,CAAC;QAC/C,OAAO;YACL,GAAG,CAAC,KAAK,EAAE,CAAC;gBACV,MAAM,SAAS,GAAkB,EAAE,CAAC;gBACpC,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;gBAC1B,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,GAAG,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;oBACjC,KAAK,MAAM,CAAC,IAAI,GAAG;wBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACnC,CAAC;gBACD,OAAO,GAAG,CAAC;YACb,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pond-ts",
3
- "version": "0.14.2",
3
+ "version": "0.14.3",
4
4
  "description": "TypeScript-first time series primitives",
5
5
  "license": "MIT",
6
6
  "repository": {