Write daily IRs for incremental aggregation - #998
Open
kambstreat wants to merge 67 commits into
Open
Conversation
pengyu-hou
reviewed
Jun 10, 2025
| s"${aggregationPart.inputColumn}_$opSuffix${aggregationPart.window.suffix}${bucketSuffix}" | ||
|
|
||
| def incOutputColumnName = | ||
| s"${aggregationPart.inputColumn}_$opSuffix${bucketSuffix}" |
Collaborator
There was a problem hiding this comment.
should we still keep the aggregationPart.window.suffix? Otherwise, how do we reconstruct the final output column?
Contributor
Author
There was a problem hiding this comment.
@pengyu-hou not sure I get it. I can not use the window.suffix right as the intermediate incremental is daily aggregation.
pengyu-hou
reviewed
Jun 10, 2025
| .toArray | ||
| .zip(columnAggregators.map(_.irType)) | ||
|
|
||
| val incSchema = aggregationParts |
Collaborator
There was a problem hiding this comment.
I am thinking that we should use a full incremental in the code and we can keep inc as the suffix for the table so the table names are not getting too long. What do you think?
Suggested change
| val incSchema = aggregationParts | |
| val incrementalSchema = aggregationParts |
Contributor
Author
There was a problem hiding this comment.
yes. good to use full name.
pengyu-hou
reviewed
Jun 20, 2025
pengyu-hou
reviewed
Jul 17, 2025
…hronon into kchakka/incremental
Co-authored-by: Pengyu Hou <3771747+pengyu-hou@users.noreply.github.com> Signed-off-by: chaitanya <1847554+kambstreat@users.noreply.github.com>
…hronon into kchakka/incremental
…hronon into kchakka/incremental
…hronon into kchakka/incremental
….scala
GroupBy.scala — bug fixes
- Fix aggregationParts returning empty when no holes exist: Previously derived from the hole-filling loop, so when the incremental table was already up-to-date, convertIncrementalDfToHops received an empty list
and produced wrong hops. Now derived directly from groupByConf unconditionally.
- Fix maxWindow.get NPE: Replaced with getOrElse(throw ...) that gives a clear error message when incremental mode is used on a GroupBy with no windowed aggregations.
- Remove unused imports: Operation and com.google.common.collect.Table.
- Fix typo: "aggregatiosn" → "aggregations" in scaladoc.
GroupByIncrementalTest.scala — test improvements
- Enable UNIQUE_COUNT and BOUNDED_UNIQUE_COUNT: Uncommented both operations in testIncrementalBasicAggregations. Comparison converts array IRs to their sizes (since element ordering/MD5 hashing differs between
Chronon and SQL) and validates against count(distinct ...) from SQL.
- Rewrite testIncrementalStatisticalAggregations: Replaced schema-only checks with a full incremental-vs-non-incremental snapshotEvents comparison for SKEW, KURTOSIS, APPROX_PERCENTILE, APPROX_UNIQUE_COUNT, and
APPROX_HISTOGRAM_K.
- Remove redundant column-existence assertions: Dropped assertTrue("IR must contain X") checks that were superseded by the value comparison.
- Remove unused BinaryType import.
pengyu-hou
added a commit
that referenced
this pull request
Jun 11, 2026
Introduces an `is_incremental` mode for SNAPSHOT event GroupBy backfills. Per-day partial aggregates (IRs) are cached in a `<table>_daily_inc` table so the expensive raw-event hop aggregation is computed once per day and reused across backfill steps, rather than re-scanning the full window on every step. Squashed from PR #998 (kambstreat/chronon kchakka/incremental) onto main, to serve as the base for continued ownership and follow-up improvements. Co-authored-by: chaitu <kambstreat@users.noreply.github.com> Co-authored-by: Pengyu Hou <3771747+pengyu-hou@users.noreply.github.com>
pengyu-hou
added a commit
that referenced
this pull request
Jun 11, 2026
Introduces an `is_incremental` mode for SNAPSHOT event GroupBy backfills. Per-day partial aggregates (IRs) are cached in a `<table>_daily_inc` table so the expensive raw-event hop aggregation is computed once per day and reused across backfill steps, rather than re-scanning the full window on every step. Squashed from PR #998 (kambstreat/chronon kchakka/incremental) onto main, to serve as the base for continued ownership and follow-up improvements. Co-authored-by: chaitu <kambstreat@users.noreply.github.com> Co-authored-by: Pengyu Hou <3771747+pengyu-hou@users.noreply.github.com>
pengyu-hou
added a commit
that referenced
this pull request
Jun 11, 2026
Introduces an `is_incremental` mode for SNAPSHOT event GroupBy backfills. Per-day partial aggregates (IRs) are cached in a `<table>_daily_inc` table so the expensive raw-event hop aggregation is computed once per day and reused across backfill steps, rather than re-scanning the full window on every step. Squashed from PR #998 (kambstreat/chronon kchakka/incremental) onto main, to serve as the base for continued ownership and follow-up improvements. Co-authored-by: chaitu <kambstreat@users.noreply.github.com> Co-authored-by: Pengyu Hou <3771747+pengyu-hou@users.noreply.github.com>
pengyu-hou
added a commit
that referenced
this pull request
Jun 12, 2026
* Add incremental SNAPSHOT GroupBy aggregation Introduces an `is_incremental` mode for SNAPSHOT event GroupBy backfills. Per-day partial aggregates (IRs) are cached in a `<table>_daily_inc` table so the expensive raw-event hop aggregation is computed once per day and reused across backfill steps, rather than re-scanning the full window on every step. Squashed from PR #998 (kambstreat/chronon kchakka/incremental) onto main, to serve as the base for continued ownership and follow-up improvements. Co-authored-by: chaitu <kambstreat@users.noreply.github.com> Co-authored-by: Pengyu Hou <3771747+pengyu-hou@users.noreply.github.com> * Make incremental IR build chunked/restartable and skew-resilient Two scalability fixes to the incremental SNAPSHOT GroupBy path, surfaced by benchmarking a ~500M-rows/day, 730-day-window source that OOM'd the cold build: 1. Chunk the daily-IR hole filling by stepDays. computeIncrementalDf now splits each unfilled hole into PartitionRange.steps(stepDays) sub-ranges, each committed separately. This makes the large cold / full-recompute build restartable (resumes via unfilledRanges) and bounds per-write shuffle/memory instead of materializing the whole window in one insertInto. stepDays is threaded from computeBackfill through fromIncrementalDf; default None keeps the original single-write behavior. 2. Replace groupByKey with map-side-combining aggregateByKey in convertIncrementalDfToHops, so a hot key's daily hops are merged incrementally rather than shuffled to one task and held as a single Iterable. Output type and ordering are unchanged. Adds testIncrementalChunkedBuild which runs computeBackfill in incremental mode with stepDays and asserts the output equals a non-incremental backfill. Existing correctness tests (which compare incremental vs non-incremental across all IR types) remain green. * Reduce per-row overhead in incremental hops conversion Two behavior-preserving micro-optimizations in convertIncrementalDfToHops, which runs once per cached IR row (~hundreds of millions on large windows): - Hoist convertSparkToJava from a per-row inner `def` to an object-level method so it is no longer re-allocated for every row. - Resolve the partition column index and the per-aggregation IR column indices once on the driver, instead of doing a name->index fieldIndex lookup per column per row. Indices (and partitionSpec) are captured in the executor closure rather than the DataFrame. No change to computed values; the existing incremental-vs-normal correctness tests across all IR types remain green. * Fix incremental IR write clobbering neighbor partitions computeIncrementalDf ran hopsAggregate over a window-widened source scan (getIntersectedRange pushes the read back by the query window), so it emitted daily IR hops for days outside the requested range. With dynamic partition overwrite, save() then wrote ALL of those partitions - so filling one day's hole overwrote neighboring partitions with window-truncated, incomplete data, gutting them to a few rows. This caused systematic, one-directional event loss in the final GroupBy output (observed at scale: a different day silently reduced to ~hundreds of rows per run, ~16M undercounted keys downstream). It was invisible to existing tests because their narrow ranges recomputed the clobbered neighbors correctly. Fix: clamp the IR write to exactly the requested range before save(), so only the target partition(s) are ever written. The widened input scan is still needed for correct per-day aggregation; only the output is clamped. Adds testIncrementalWriteIsClampedToRange: a single-day computeIncrementalDf over a 30-day input must write exactly one partition (writes 26 without the fix). * Split incremental build from read (materialize vs fromIncrementalDf) Separates the two halves of incremental mode so the _daily_inc table can be built once and consumed by multiple readers (backfill, upload, later join): - Rename the object-level computeIncrementalDf -> materializeIncrementalDf: the idempotent "producer" that fills _daily_inc holes for [range.start - maxWindow, range.end]. No-op when already complete. - fromIncrementalDf becomes a mode-agnostic read adapter (_daily_inc -> GroupBy with overridden hopsAggregate) and gains buildIfMissing (default true): * true = ensure-then-read (ad hoc / standalone; current behavior unchanged) * false = read-only (production, behind a dedicated producer node that is the sole writer; consumers run as pure readers) Pure refactor: computeBackfill and all existing callers use the default buildIfMissing=true, so behavior is unchanged. The instance computeIncrementalDf (per-hole worker) is untouched. All GroupByIncrementalTest cases pass. This is the seam for follow-up PRs: snapshot-events incremental upload and join right-parts consume fromIncrementalDf; production wires materializeIncrementalDf as a producer node the consumers depend on. * Keep computeIncrementalDf name; trim redundant comments - Revert the object-level method name back to computeIncrementalDf (from materializeIncrementalDf); it coexists with the instance-level overload as before. Docstring disambiguates the two. - Remove comments that restate the code (per-branch line/step narration, type-conversion case labels, @PARAM boilerplate) while keeping the "why" comments: the write clamp rationale, aggregateByKey skew resilience, the finalize=false note, and the closure/serialization notes. No behavior change; all GroupByIncrementalTest cases pass. * scalafmt * restrict to snapshot + event * Address review: tag incremental table, fix accuracy guard, require daily resolution Responds to PR review feedback: - Tag the _daily_inc table as Chronon-generated with a dedicated table type. Adds Constants.TableType.GroupByIncremental and tags the write in computeIncrementalDf (was previously untagged, unlike the backfill output). - Fix the Python is_incremental accuracy guard: accuracy is optional and inferred as SNAPSHOT when unset (absent a streaming source). The guard now accepts the inferred-SNAPSHOT case instead of rejecting accuracy=None. The Scala backstop already uses inferredAccuracy. - Require DailyResolution in the instance computeIncrementalDf, since the incremental IRs are daily; guards against a non-daily resolution silently producing wrong IRs. Also de-flakes testIncrementalFirstLast: it perturbed ts with rand() to make timestamps unique, but rand() can collide, leaving FIRST/LAST tie-breaks ambiguous. Replaced with a deterministic per-day row-number offset so no two events in a day share a ts. Verified stable across repeated runs. * Dedup shared daily IR columns in incremental mode Aggregations sharing the same (operation, input_column, bucket) but differing only by window collapse to the same daily IR column name, since the incremental column name drops the window suffix (e.g. SUM(price, 5d) and SUM(price, 10d) both -> price_sum). This is valid in normal mode but previously produced duplicate columns in _daily_inc. The collapsed IR values are identical, so persist one column per distinct name (uniqueIncrementalIndices) on both the schema and the written values. The read path resolves each part by column name, so all collapsed windows map back to the single shared column and the windowed output is reconstructed correctly. Adds testIncrementalDedupsSharedColumns: verifies a single price_sum column is written and the final windowed output (price_sum_5d / price_sum_10d) matches the non-incremental backfill. Addresses PR review comment on Extensions.scala (duplicate columns across windows). * Guard resolution in overridden hopsAggregate Follow-up to the DailyResolution check: the incremental GroupBy's overridden hopsAggregate ignores its resolution argument and always returns the cached daily hops. Add require(resolution == DailyResolution) inside the override so a non-daily consumer (e.g. a TEMPORAL upload calling hopsAggregate with FiveMinuteResolution) fails loudly instead of silently receiving daily hops as if finer-grained. * Trim verbose comments, remove spark.stop() from tests, scalafmt - Condense the wordier incremental-mode comments/scaladocs in GroupBy.scala to one or two lines while keeping the rationale. - Remove spark.stop() from GroupByIncrementalTest cases: it shut down the shared singleton SparkSession mid-suite, causing cross-test flakiness (an unrelated test could fail in a full-suite run). Verified the suite is stable across repeated full runs. - scalafmt. --------- Co-authored-by: chaitu <kambstreat@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR address the following CHIP : #984
Why / Goal
The goal of the PR is to reduce the time taken to compute aggregations. Added daily level partial aggregates for batch feature computation, which avoids reading past day's event level data.
With this change, we see, 4x improvement for computing features. (except first time run )
Test Plan
Checklist
Reviewers