# v0.24.0 — Join Correctness and Durability Hardening > **Full technical details:** [v0.24.0.md-full.md](v0.24.0.md-full.md) **Status: ✅ Released** | **Scope: Large** (~8–9 weeks) > The final resolution of the EC-01 join correctness bug, a two-phase > commit that eliminates crash-recovery data loss, configurable change > buffer durability, TOAST-aware change detection, and automatic cleanup > of refresh history. --- ## What problem does this solve? Despite multiple partial fixes since v0.12.0, the EC-01 phantom row bug in multi-table LEFT/RIGHT/FULL JOINs was not fully resolved for all scenarios. Additionally, a crash between the change buffer TRUNCATE and the frontier update could cause changes to be replayed on restart. For deployments where the change buffer absolutely must survive a crash, there was no configuration option. --- ## EC-01: Complete Join Correctness Fix The root cause of EC-01 was that Part 1b of the join delta algorithm (which handles the case "left side changed, right side unchanged") was computing its row identity hash differently from Part 1a (which handles "right side changed, left side unchanged"). When both parts ran, they should cancel each other out to zero net change for unchanged rows — but different hashes meant they did not cancel. The fix ensures both parts use the same hash algorithm (left-side primary key only). Cross-cycle phantom rows — rows from previous refresh cycles that accumulated over time — are cleaned up by the PH-D1 phantom cleanup pass. TPC-H Q15 is now re-enabled in IMMEDIATE mode. A 5,000-iteration property test verifies that no phantom accumulation occurs across any combination of INSERT, UPDATE, and DELETE sequences. *In plain terms:* LEFT JOINs, RIGHT JOINs, and FULL OUTER JOINs are now permanently correct in differential mode under all combinations of changes. --- ## Two-Phase Frontier Commit The "frontier" is the watermark recording how far into the change history a stream table has been refreshed. Previously, the frontier was updated *after* the change buffer was truncated. A crash between truncation and frontier update meant changes would be replayed from the wrong position on restart. The two-phase commit writes a *tentative* frontier before truncation and *finalises* it after the MERGE commits. On startup after a crash, any unfinished tentative frontier is reconciled. This eliminates the crash recovery window. --- ## Configurable Change Buffer Durability The `pg_trickle.change_buffer_durability` GUC provides three options: - **`unlogged`** (default) — fastest, but change buffer is empty after a crash (changes are recaptured from source tables) - **`logged`** — WAL-logged change buffers, survive crash with no data loss - **`sync`** — logged plus synchronous commit for maximum durability Deployments with strict recovery requirements can now choose the right tradeoff between performance and durability. --- ## TOAST-Aware CDC Hashing PostgreSQL uses **TOAST** (The Oversized-Attribute Storage Technique) to store large values — long text, large JSON documents, binary data — out of line. The row-identity hash used by change capture was not correctly including TOAST column data, which meant that an UPDATE changing only a TOAST column (e.g. updating a JSONB column) could be silently missed. The fix includes the size and a hash of TOAST column values in the row identity hash, ensuring that all column changes are captured regardless of their storage mechanism. *In plain terms:* if your source tables use large JSON, XML, or binary columns, changes to those columns are now reliably detected. --- ## Refresh History Retention `pg_trickle.refresh_history_retention_days` (default 7) configures how long rows are kept in `pgt_refresh_history`. The background worker automatically prunes rows older than the retention period in small batches during idle ticks, preventing the history table from growing without bound. --- ## Frozen Stream Table Detector The `df_frozen_stream_tables` self-monitoring view flags any stream table that has not refreshed within 5× its configured refresh interval while its source tables are showing active change capture. This detects stuck or broken stream tables that are silently falling behind, and emits a `pgtrickle_alert` notification. --- ## Scope v0.24.0 closes the last known correctness gap (EC-01 join phantoms), adds a durability guarantee against crash-recovery data loss (two-phase frontier), and provides the operational tools (retention, frozen detector) to keep pg_trickle healthy over months of production operation.