Ctrl K
ComunidadGuías…

docs/specs/FS-20251222-axis-chart-performance.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Axis Chart Performance & Streaming Data Pipeline

## Overview
Large datasets (e.g., Firestore collections, API payloads) currently load fully into memory before `AxisChart` runs. The aggregation pipeline repeatedly parses dates with `moment`, scans arrays with `lodash`, and builds intermediate arrays for filters and legends, which stalls dashboards and exhausts memory. This spec defines a set of incremental backend improvements that preserve existing chart outputs while dramatically reducing CPU and memory usage:

1. Stream dataset rows from `DatasetController.runRequest()` to the chart pipeline.
2. Normalize date values once per row using existing field selector heuristics.
3. Replace array-based aggregation with map/set structures keyed by axis values.
4. Consolidate redundant filter passes and lazy-build condition metadata.
5. Share resolved date windows between ChartController and AxisChart.

All changes must respect the current field selector logic (`root[].path`, API auto-detection) so non-uniform payloads continue to work.

## Goals
- Enable `ChartController.updateChartData()` to start processing dataset rows before the entire response arrives, lowering latency and memory.
- Reduce per-row CPU cost inside `AxisChart` by normalizing dates once and reusing numeric timestamps for comparisons.
- Ensure filters and conditions run in a single pass per dataset while supporting the existing condition builder UX.
- Maintain backward-compatible chart outputs (legends, tooltips, bins) even as internal data structures change.
- Provide regression tests comparing legacy vs. optimized outputs for representative chart types and data sources.

## Non-Goals
- No redesign of the dataset schema or chart builder UI (handled in other specs).
- No changes to the determineType heuristics; we reuse them.
- No new aggregation types or chart features—strictly performance/architecture.

## Streaming Dataset Execution
- Update `DatasetController.runRequest()` to optionally return an async iterator (`for await (row of datasetStream)`), emitting normalized rows as soon as source data arrives.
- For SQL/Mongo: leverage cursor APIs; for APIs/Firestore: paginate via existing mechanisms, emitting each batch.
- Default behavior remains returning full arrays. A new flag (`stream: true`) enables streaming and is only used by `ChartController` for axis charts at first.
- Ensure field selectors (`root[].path`) apply per row before emitting to the stream so AxisChart always receives uniform `{ data, options }` objects.
- Add back-pressure support: AxisChart signals when it’s ready for more rows to avoid overwhelming memory.

## Date Normalization
- Introduce `modules/dateOps.js` with helpers:
  - `parseDate(value, timezone, formatHint)` → returns numeric timestamp + cached `moment` if needed.
  - `bucketTimestamp(ts, timeInterval)` → integer bucket key.
  - `isWithinRange(ts, startTs, endTs)` using cached `moment` comparisons only when necessary.
- AxisChart uses the selector to extract a raw value, then calls `parseDate` once per row, storing the timestamp in a local cache keyed by row+field.
- `ChartController` computes chart-level `startDate`/`endDate` once (respecting timezone, `currentEndDate`, `fixedStartDate`) and passes numeric timestamps plus `moment` objects down to AxisChart.
- Preserve existing mix of `moment` and native `Date` where benchmarks dictate (e.g., `moment.isBefore` for comparisons) but centralize the choice inside `dateOps`.

## Map-Based Aggregation
- Replace array scans inside AxisChart with `Map`/`Object` keyed by X-axis bucket:
  - `const xBuckets = new Map();`
  - Each bucket stores aggregated Y values, counts, min/max needed for tooltips/goals.
- After processing all rows (or streaming completion), convert the map to sorted arrays for Chart.js (`labels` + `datasets`).
- Use stable key generation (stringified bucket number or category) to ensure deterministic ordering.
- Ensure `conditionsOptions` collection uses maps to avoid duplicates and build values lazily.

## Filter Consolidation
- Combine dataset conditions, dashboard filters, and exposed filters into a single predicate pipeline per row:
  - Precompute active filters before streaming starts.
  - For each row, run the combined predicate once; if it passes, apply aggregations.
- `conditionsOptions` only populate for fields marked `exposed` or when the UI explicitly requests them.
- Add memoization for field selectors when multiple filters reference the same field, so we don’t call `_.get` repeatedly.

## AxisChart / ChartController Changes
- `ChartController.updateChartData()`:
  - Pass resolved `dateWindow` `{ startTs, endTs, startMoment, endMoment }` and timezone to AxisChart.
  - When `stream: true`, provide AxisChart with dataset async iterators instead of arrays; AxisChart consumes them sequentially.
  - Continue caching per dataset; when streaming, buffer the final normalized dataset if caching is enabled.
- `AxisChart.plot()`:
  - Detect stream vs. array input; wrap array inputs with a simple iterator for code reuse.
  - Use the new date ops and map aggregation.
  - Provide hooks (`onChunkProcessed`) for telemetry to measure chunk sizes and processing time.

## Testing & Validation
- Snapshot tests: run existing representative datasets (Mongo, SQL, API) through both legacy and new paths, assert identical chart configuration output.
- Stress tests: synthetic dataset with >1M rows streamed to ensure memory stays bounded and processing time decreases.
- Feature flag `axisChartPerf` gates new behavior; allow per-team rollouts.

## Telemetry
- Track `axis_stream_enabled`, `axis_stream_chunks_processed`, `axis_date_parse_time_ms`, `axis_memory_peak_mb`.
- Emit warnings when fallback to non-streaming occurs (e.g., unsupported connection type).

## Risks & Mitigations
- **Field selector regressions**: keep selector logic untouched; streaming yields already-selected rows to avoid duplicating logic.
- **Cache complications**: when streaming, ensure final aggregated data is stored for cache usage; otherwise disable caching for that run and log.
- **Library mix**: centralize the “best” operations in `dateOps` and cover with tests so future contributors don’t unknowingly degrade performance.
- **Backpressure**: implement simple buffer limits (e.g., process 5k rows at a time) and pause upstream cursors if AxisChart lags.