Skip to content

fix(streaming): real recent-window dedup, dtype-stable row hashes, drift for constant columns - #357

Open
kevincostner17 wants to merge 1 commit into
mainfrom
fix/streaming-dedup-and-drift
Open

kevincostner17 wants to merge 1 commit into
mainfrom
fix/streaming-dedup-and-drift

Conversation

@kevincostner17

Copy link
Copy Markdown
Contributor

Summary

Fixes three streaming bugs in StreamingCleaner.

  • global_duplicates keeps the first window_size rows forever instead of a recent window #292: global_duplicates kept the first window_size rows forever.
    • Cause: the cross-batch hash set only accepted new hashes while it had room and never evicted anything. After window_size distinct rows, duplicates of recent rows passed through for the rest of the stream.
    • Fix: the window is now an OrderedDict. A repeated row moves to the most-recent end, and the least recently seen rows are evicted, so it is the bounded recent window the docs describe.
    • Duplicates within a batch are handled as before.
  • global_duplicates misses cross-batch duplicates when a column flips int64/float64 #293: cross-batch duplicates were missed when a column flipped between int64 and float64.
    • Cause: hash_pandas_object hashes dtype-specific bytes, so 1 and 1.0 hashed differently (for example, an int column promoted to float by a missing value).
    • Fix: rows are now hashed with numeric non-bool columns (nullable ones included) converted to float64, missing values as NaN, and -0.0 as 0.0.
    • Integer columns holding values beyond ±2**53 keep their dtype, so distinct large integers are never merged.
  • Streaming distribution drift never fires for a column that was constant in earlier batches #294: distribution drift never fired for a column that had been constant.
    • Cause: the z-score check returned early when the running std was 0.
    • Fix: for a constant column with at least two prior values, a batch mean that moves away from the constant by more than 1e-9 * max(1, |mean|) is now reported as high-risk distribution drift.

Docstrings for global_duplicates and drift_zscore, and docs/streaming.md, are updated to match.

Behaviour changes:

  • Duplicates of recent rows are now removed for the whole stream, and rows older than the window can reappear.
  • Int/float dtype flips no longer hide duplicates.
  • A constant column that changes now raises a drift action, a warning and a drift_log entry.
  • No serialized format changes: the seen-hash window is in memory only, not part of state_, StreamingState.to_dict() or any checkpoint.

Tests

New module tests/test_streaming_dedup_drift.py (15 tests):

10 of the 15 fail on main; the other 5 guard against false positives.

Verification

  • ruff check .: all checks passed
  • mypy src/freshdata: no issues in 202 source files
  • pytest -m "not online and not large", py3.12 / pandas 2.3: 4254 passed, 6 skipped
  • pytest -m "not online and not large", py3.9 / pandas 1.5: 4250 passed, 10 skipped
  • All three issue repros were confirmed on both lanes before the fix and pass after.

Closes #292
Closes #293
Closes #294

…t-column drift

global_duplicates kept the first window_size rows forever (#292). The
cross-batch hash set only accepted new hashes while it had room and never
evicted, so once window_size distinct rows had been seen it stopped
remembering, and duplicates of recent rows passed through for the rest of
the stream. The window is now an OrderedDict: a repeated row is moved to
the most-recent end, new rows are appended, and the least recently seen
rows are evicted once the window exceeds window_size. Membership is still
checked against earlier batches only, so within-batch duplicate handling is
unchanged.

Cross-batch duplicates were missed when a numeric column flipped between
int64 and float64 (#293), e.g. an integer column promoted by a missing
value, because hash_pandas_object hashes dtype-specific bytes. Rows are now
hashed from a canonical frame: numeric non-bool columns, nullable ones
included, are hashed as float64 with missing values as NaN and -0.0 as 0.0.
Integer columns holding a value beyond +/-2**53 keep their own dtype so
distinct large integers are not merged.

Distribution drift never fired for a column that had been constant (#294),
because the z-score check returned early when the running std was 0. For a
constant column with at least two prior values, drift is now reported (high
risk, kind "distribution") when the batch mean leaves the constant by more
than 1e-9 * max(1, |mean|).

The seen-hash window is in-memory only and is not part of state_,
StreamingState.to_dict() or any checkpoint, so there is no serialized
format to migrate.

Closes #292
Closes #293
Closes #294
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 55756c33-96e4-4901-8aee-fdbd68cc1b1b


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

FreshData benchmark report — performance

  • freshdata: ?
  • python: ?
  • platform: ?
fixture n_rows n_cols p50 s p95 s peak MB repair % false-repair % preserve % trust monotonic export %

Authored-code reduction (Metric 6)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant