# v0.88.0 — Vectorized Aggregates & Delta Planning

> **Status:** Implemented
> **Scope:** Large
> **User promise:** *"One PostgreSQL instance can handle a lot."*
> **Blocked by:** [v0.87.17](v0.87.17.md)
> **Renumbered and split:** previously the first half of v0.84.0 "Fast
> Incremental Engine". Incremental window functions moved to
> [v0.89.0](v0.89.0.md). Parallel delta fan-out moved past 1.0.

## Theme

Make the delta path faster without changing what users see: a vectorized
execution path for pure-aggregate stream tables, and cost-based operator
ordering inside delta queries so intermediate results stay small.

The release keeps narrow, proven fast paths. Unvalidated planner rules stay in
shadow mode, and optional optimization breadth must not delay the lifecycle
work in v0.91.0 and later.

The v0.87.1 through v0.87.6 correctness program lands first. Its exact oracle,
replay corpus, semantic coverage, schema contracts, snapshot plans, and release
gate protect each physical rewrite in this release.

The v0.87.7 through v0.87.13 lifecycle-security program also lands first. Its
stream-owner execution boundary applies to every new delta plan and physical
execution path in this release.

Before MT-8 implementation starts, commit the target benchmark workload, data
generator and scale, v0.87.17 baseline, measurement method, repetition count,
hardware profile, and memory and write-path regression budgets. A benchmark
bug may be fixed later, but the fix requires review and a new baseline. The
≥5× target applies to this frozen benchmark. Representative end-to-end
workloads and their regression budgets remain the broader admission test.

This is one of two places where deep complexity is justified, because all of it
is **inside the engine**. Users get the same extension, the same PostgreSQL, the
same SQL — just faster. Nothing here changes deployment, topology or the API.

## Items

### ENG-1: `DiffContext` Decomposition (first)

Split the 15-field `DiffContext` struct into focused sub-contexts so the engine
work below lands in code that can be reasoned about:

- `CdcContext` — frontier LSNs, source OIDs, CDC columns, key columns
- `CacheContext` — CTE delta cache, scan-pushed predicates, bypass tables
- `OptimizationContext` — depth tracking, CTE counters, max limits

The top-level `DiffContext` composes these via delegation, so no call sites
change semantics. This lands **before** the items below, which each need only a
subset of the context.

### MT-8: Vectorized Aggregate Path

For pure-aggregate STs (GROUP BY with no joins), process deltas using columnar
batches:

1. Read change buffer rows into columnar batches (1024-row pages)
2. Vectorized hash aggregation: group-by keys → aggregate accumulators
3. SIMD-friendly SUM/COUNT/MIN/MAX operations on batch columns
4. Emit only changed groups as delta output
5. Apply via batched MERGE (INSERT/UPDATE per group)

Benefits:
- Large throughput gain for SUM/COUNT/AVG aggregates (auto-vectorization)
- Cache-friendly memory access patterns (column-at-a-time processing)
- Reduced SPI overhead (bulk operations instead of per-row)

Implementation:
- New `VectorizedAggregateOperator` in `src/dvm/operators/vectorized_agg.rs`
- Activated when OpTree is a single Aggregate node over a Scan (no joins)
- Falls back to standard SQL-based path for complex trees

**Dependency decision required first.** The original plan reached for
`arrow-array` and `arrow-compute`. v0.76.0 deliberately removed
`arrow-array`, `arrow-schema`, `parquet`, `object_store` and `bytes` to cut
compile time and attack surface. Re-adding two of them needs an ADR that either
reverses that decision explicitly, with the compile-time and audit-surface cost
measured, or selects a narrower implementation — plain `Vec<T>` columns over
PostgreSQL datums are enough for SUM/COUNT/MIN/MAX and add no dependency at
all. **The ADR is a gating deliverable of this release**, and the default
answer is the dependency-free one until Arrow is shown to be worth it.

### LT-9: Cost-Based Operator Scheduling

Develop cost-based operator scheduling in three stages.

1. **Plan instrumentation.** Record estimated and actual cardinalities,
   intermediate-row counts, and runtime for the current plan. Record candidate
   runtime when the benchmark or test environment can execute both plans.
2. **Shadow planning.** Propose safe alternative orders without selecting them
   by default. CI and benchmarks report prediction accuracy, expected wins,
   regressions, and the worst regression.
3. **Controlled enablement.** Select an alternative automatically only for an
   operator class whose benchmark and soak evidence meets its regression
   budget. Keep every other class on the existing planner.

Candidate rules include selective-filter pushdown, inner-join input ordering,
and moving `DISTINCT` across a proven one-to-one projection. NULL semantics and
outer-join boundaries remain hard safety constraints.

Before pg_trickle adds a custom cost optimizer, an ADR must explain why the
delta query cannot be expressed in a relational form that lets PostgreSQL's
optimizer make the same decision. A custom optimizer is justified only when
delta semantics hide information from PostgreSQL.

`pgtrickle.explain_delta_plan(pgt_id)` reports estimates, observations, the
candidate order, its validation status, and the plan selected for execution.

### PERF-O4: Parallel Delta Computation Fan-Out — **moved past v1.0**

The original plan was to "spawn parallel SPI connections (one per branch)" for
multi-source joins. SPI is not a connection pool: it executes inside the calling
backend's transaction and is neither thread-safe nor re-entrant across threads,
so this cannot be implemented as written. A real version needs either
PostgreSQL parallel workers driving the delta query (which the planner already
does inside a single delta statement) or the background-worker/`dblink` fan-out
that belongs with the decoupled-compute work.

It is therefore recorded in [v1.7.0](v1.7.0.md-full.md) rather than kept in the
pre-1.0 path with an implementation strategy that does not exist. Nothing else
in this release depends on it.

## Exit criteria

- [x] `DiffContext` decomposed first with no behavioural change in the E2E suite
- [x] ADR published deciding the vectorized-aggregate dependency question, and
      the v0.76.0 dependency-removal rationale explicitly addressed
- [x] Target benchmark contract committed before MT-8 implementation begins
- [x] Vectorized aggregate path active for pure-aggregate stream tables, with
      identical results verified against the FULL oracle
- [x] End-to-end refresh throughput improves on representative workloads with
      no meaningful write-path or memory regression
- [x] Planner estimates are compared with actual cardinalities and runtime.
      `explain_delta_plan()` exposes both
- [x] Automatic reordering is enabled only for validated operator classes. An
      unvalidated class keeps the existing plan
- [x] The target aggregate benchmark improves by at least 5×, and eligible
      production-like workloads regress by no more than 10%. Otherwise, the
      eligibility rule excludes the regressing shape
- [x] Both paths validated by the DVM delta-invariant validator from v0.77.0
