fix: epoch-encoding bugs — timestamp equality/IN returns ZERO rows on compressed partitions - #4
Merged
Conversation
Two Unix-epoch vs PostgreSQL-epoch (offset 946,684,800 s) confusions in src/scan/exec/segments.rs: 1. Bloom probe encoding (live bug): the per-segment bloom BUILD side (compress.rs) hashes TypedColumn values, which store timestamps/dates as Unix-epoch microseconds — but the probe side hashed the raw PG-epoch datum of the qual constant. The hashes never matched, every segment was falsely bloom-rejected, and `ts = const` / `ts IN (...)` on timestamp/date columns returned ZERO rows on compressed partitions. New bloom_probe_encode() converts the constant into the build-side domain (also normalizing the f32 bit-pattern case the Eq path previously special-cased and the InList path missed). 2. Meta-table min/max encoding (latent): load_segments_heap stored the time column's meta `_min_`/`_max_` identity-encoded (PG-epoch datum) in ColMinMax, while every consumer (decode_encoded_to_pg_i64, segment_all_rows_pass, constant_extract_key_for_segment) decodes the colstats encoding (Unix-epoch us) — timestamps 30 years early. On current main all reachable consumers either pass load_minmax=false (correct block below) or get the value overwritten by the colstats load, so no wrong results are reachable today — but the contract violation bites any new consumer of the map. Convert at load, matching the colstats encoding contract. Regression tests: bloom_probe_encode_matches_build_domain unit test + TestEpochEncoding integration class (timestamp sort column with year-2000-adjacent constants: equality, IN-list, absent-value, DATE payload equality, MIN/MAX aggregate shapes, boundary range predicates). The equality/IN tests return zero rows without fix #1. Extracted from the perf/clickhouse-gap-session branch (commit 7ea1b97 and the bloom-probe portion of fcfb1dc). (cherry picked from commit b3bcc69)
…epoch bug in in-list minmax filter The colstats min/max in-list filter identity-encoded raw PG-epoch datums for timestamp/date IN-lists on non-time columns while comparing against Unix-epoch-microsecond colstats bounds - falsely pruning every segment (zero rows). Route all constant encoding (eq, range, in-list, bloom probe) through encode_datum_to_i64, and skip minmax pruning entirely when an element is unencodable rather than risk a wrong prune. (cherry picked from commit b579349)
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.
Confirmed live on
postgres-ovh-east(dot_token)Any equality or IN-list predicate on a timestamp column against a compressed partition silently returns zero rows. Range predicates are correct, so this is invisible unless you look for it.
Blast radius: 625 compressed partitions across both deltatables (541 pingbacks + 84 telemetry_request_cost).
Root cause
Unix-epoch vs PostgreSQL-epoch (946,684,800 s offset) confusion in
src/scan/exec/segments.rs— fixed upstream, never picked onto this branch:b3bcc69ca— the bloom build side (compress.rs) hashesTypedColumnvalues as Unix-epoch µs, while the probe side hashed the raw PG-epoch datum of the qual constant. Hashes never match → every segment falsely bloom-rejected. Also converts the meta-table min/max at load to match the colstats encoding contract (latent today, bites any new consumer).b57934975— third epoch bug: the colstats min/max in-list filter identity-encoded PG-epoch datums for timestamp/date IN-lists on non-time columns, comparing against Unix-epoch-µs bounds → falsely pruned every segment. Routes all constant encoding (eq, range, in-list, bloom probe) throughencode_datum_to_i64, and skips minmax pruning entirely when an element is unencodable rather than risk a wrong prune.This PR
Cherry-picks both upstream commits.
src/scan/exec/segments.rsapplied clean; the only conflict was an append-at-EOF collision intests/test_compression.pybetween this branch's cross-schema tests and upstream's newTestEpochEncodingclass — resolved by keeping both (no duplicate test names, file parses).Brings upstream's regression coverage:
bloom_probe_encode_matches_build_domainunit test +TestEpochEncoding(equality, IN-list, absent-value, DATE payload equality, MIN/MAX aggregate shapes, boundary ranges). The equality/IN cases fail without the fix.No version bump —
0.2.0-dotcms.6is already on the branch from #3 and is not yet tagged or released, so both fixes ship in one deb build and one image roll instead of two primary restarts.Post-roll acceptance test
Re-run the exact probes above against the same two partitions; every
0must become1. Existing compressed partitions need no recompression — this is a read/probe-path fix only.Upstream audit — what else was considered and skipped
dotcms-0.2.0is v0.2.0 + 16 local commits; upstreammainis 83 commits ahead. Reviewed all 83:1897f4c8fjsonb segment restorerestore_segment_rowsis DML-era code that doesn't exist on 0.2.0; this branch's decompress path already routes jsonb throughjsonb_binary_to_text(dotcms.5)e41983706sum with NaN literals5e800a8a5xataio#53RenderGucGuardDateStyle/extra_float_digits78ca3bf09xataio#45 per-table error isolationworker.rsthat collide with this branch's custom launcherStrategic note: upstream now carries its own
target_database(xataio#38/xataio#39), this fork's non-text-column fix (xataio#51), and the ProcessUtility guard (xataio#25). The fork's unique delta is down to roughly the launcher pattern and version packaging — at the next upstream release, rebasing onto upstreammainlikely costs less than continuing to cherry-pick onto the 0.2.0 base.Next steps (not done here)
Merge → tag
v0.2.0-dotcms.6on the merge commit (captures #3's ANALYZE guard and this) →release.ymlbuilds the debs → bumpARG PG_DELTAX_VERSIONinovh-k8s-cluster'spostgres-cnpg/Dockerfile(validate check races the release; re-run once assets exist) → digest-pinned roll ofpostgres-ovh-east.🤖 Generated with Claude Code
https://claude.ai/code/session_01XGCecJYCz8dMTNPadEbeRf