Conversation
This will be basis for keeping some time series tracking of catalog updates, in meantime this allows exact comparisons with pg_last_wal_replay_lsn Also enforce shadow catalog is using streaming replication, avoids deadlocks
Replace live catalog cache & invalidation tracking with durable relation descriptor log Log checkpoints at commit boundaries. This allows looking up relation id at a given LSN
41526a5 to
7e0938b
Compare
|
PLAN_NO_SKIP_LOOP.md Last commit was built over the course of a 12 hour 35 iteration /loop |
Build and validate each committed transaction before applying schema, config, or ClickHouse side effects. Freeze descriptors and routes in a checksummed plan, spill large plans to disk, then replay only after planning succeeds Defer records from catalog-dirty transactions until commit, decode them against committed descriptors, and fail ambiguous layouts or unsupported WAL shapes instead of silently dropping rows
…dable Capture marks span it can't prove decodable when a transaction changes a table in place. Stash resolution asks log at the commit's next_lsn, past that span's end, so it always got a clean Present back and rows written inside the span decoded under the final descriptor anyway Only mark a span when bytes really change. typmod and attstorage moves are declared shape only. Tuple walk reads attlen/attalign/attbyval, so keeps early valid_from and publish nothing. Publish span per key verdict can name, filenode and oid, so decode and truncate see same fence Close the neighbouring holes the same way. A filenode landing inside a span with no XLOG_SMGR_CREATE marker fails closed instead of being tracked without payload and silently dropped at commit. Draining stashed records with no verdict installed fails closed instead of sending every one through the discard arm. An aborted tree's verdict is removed so the next transaction reusing that xid can't fold rows under it Stashed records drop block images on blocks that also carry data, without this a dirty COPY spills its whole WAL volume Run descriptor log compaction on its own task, fed each persisted floor over a watch channel. Inline on the pump it rewrote the checkpoint while no keepalive was answered, long enough to hit the source's wal_sender_timeout. Tail-bytes gauge reads a mirror so it no longer dips to zero for the length of a compaction Split plan-failure counter by cause so an unmodelled WAL op is distinguishable from spill error
| /// Load `desc_log.ckpt` + `desc_log.tail` under `dir`, repairing a torn | ||
| /// tail. Creates an empty tail (header only) when absent. Never creates | ||
| /// the ckpt — [`Self::seed`] and GC own it. | ||
| pub async fn open(dir: &Path, identity: DescLogIdentity) -> Result<Self> { |
There was a problem hiding this comment.
on the restart, the log is already durable but clickhouse isn't updated yet. shadow pg is also already ahead. i have two concerns here:
- Clickhouse will not receive the relevant alter.
- What happens if before writng descriptor log we crash and shadow pg is alreayd updated?
There was a problem hiding this comment.
on restart we go by manifest, not log. manifest only advances when CH durable. In 2nd scenario we shouldn't hit that since shadow shouldn't be written due to boundary_hold.rs, but catalog_capture.rs will raise "descriptor log lost coverage" if 2nd scenario did somehow happen
There was a problem hiding this comment.
can add some tests:
- Crash after shadow reaches boundary, before descriptor append
- Crash after descriptor fsync, before commit enters worker queue
- Crash after commit queues, before ClickHouse DDL begins
- Crash after first ClickHouse ALTER statement, before remaining statements
- Crash after complete ClickHouse DDL, before emitter acknowledgment
- Crash after in-memory acknowledgment, before manifest fsync
- Crash after manifest fsync, before descriptor GC
| self.stats.clone() | ||
| } | ||
|
|
||
| pub async fn capture_boundary( |
There was a problem hiding this comment.
For higher scale, we will need to think about making this hold per table or something? So that alter in one table doesn't affect other tables.
There was a problem hiding this comment.
yes, https://github.com/ClickHouse/walshadow/blob/be-modular/plans/future/custom_rmgr.md seeks to improve overall efficiency, but we'd have to loosen our LSN ordering to be per table. I've been relying on these being slow path where we keep simpler design
| /// tail. Creates an empty tail (header only) when absent. Never creates | ||
| /// the ckpt — [`Self::seed`] and GC own it. | ||
| pub async fn open(dir: &Path, identity: DescLogIdentity) -> Result<Self> { | ||
| let mut index = Index::default(); |
There was a problem hiding this comment.
Also what happens if this file gets corrupted during a restart? We will have to force resync?
There was a problem hiding this comment.
we have some recovery mechanisms: detecting corrupt tail replays beforehand, duplicate detection between checkpoint & tail (checkpoint written before tail, crash in between could cause this), but malformed checkpoint yeah resync
| "decoder worker terminated during catalog boundary hold at {commit_lsn:#X}" | ||
| )); | ||
| } | ||
| if start.elapsed() >= self.config.hold_timeout { |
There was a problem hiding this comment.
wouldn't walsender timeout first?
There was a problem hiding this comment.
try to avoid it by having hold_timeout below wal_sender_timeout (30s vs default 60s), but we could do better about handling primary keep alives in meantime. but if this fails then we just reconnect & pull wal from s3 maybe
| if !is_commit { | ||
| return Ok(None); | ||
| } | ||
| // Local relcache invals: second oid source + capture-all trigger. |
There was a problem hiding this comment.
A missed alter can cause issues. (Due to restart or a new change by pg), should we also do an audit to make sure that the table the consistent?
There was a problem hiding this comment.
That capability would be useful, maybe create issue or plans/future
Replace live catalog cache & invalidation tracking with durable relation descriptor log
Log checkpoints at commit boundaries. This allows looking up relation id at a given LSN
Track next_lsn as EndRecPtr. Synchronize on catalog updating commits
This will be basis for keeping some time series tracking of catalog updates,
in meantime this allows exact comparisons with pg_last_wal_replay_lsn
Also enforce shadow catalog is using streaming replication, avoids deadlocks
Plan transactions before emitting rows
Build and validate each committed transaction before applying schema, config, or ClickHouse side
effects. Freeze descriptors and routes in a checksummed plan, spill large plans to disk, then replay
only after planning succeeds
Defer records from catalog-dirty transactions until commit, decode them against committed
descriptors, and fail ambiguous layouts or unsupported WAL shapes instead of silently dropping rows