Skip to content

CEP-45: Commit log and mutation journal positions conflict with each other - #5105

Open
bdeggleston wants to merge 8 commits into
apache:cep-45-mutation-trackingfrom
bdeggleston:C21646-sstable-journal-position
Open

CEP-45: Commit log and mutation journal positions conflict with each other#5105
bdeggleston wants to merge 8 commits into
apache:cep-45-mutation-trackingfrom
bdeggleston:C21646-sstable-journal-position

Conversation

@bdeggleston

Copy link
Copy Markdown
Member

Writes to a table go to either the commit log or the mutation journal, depending on their replication type and migration status. Both record their position in the same CommitLogPosition type, but positions from the two logs are not comparable. Memtables and sstables use these positions to decide what has been persisted and what needs replay. Anything holding writes from both logs therefore records a bound that does not describe its contents.

This is reachable in normal operation. Migrating a keyspace between untracked and tracked replication routes writes to both logs at once, and a commit log can be replayed after that migration has already finished.

The change makes the two domains explicit:

  • Memtables carry the log domain they accept. When a table takes both, the memtable splits into one per domain, each with its own bound, and flush writes one sstable per domain. Full reconciled data is flushed directly into the repaired set.
  • Replay routes a mutation by the log it was read from, rather than by the keyspace's current configuration.
  • Compaction keeps tracked sstables that carry coordinator log offsets away from those that do not. Merging them produces offsets that no longer describe the data, which permanently blocks reconciliation.
  • Offset clearing happens at one point, when an sstable is promoted, instead of piecemeal during compaction.
  • Verify no longer marks a corrupt sstable unrepaired on a tracked table, which would have created data that can never reconcile.

The _build-accord target otherwise runs a full gradle clean build and
publishToMavenLocal on every ant invocation. It also takes a lock on the
shared gradle artifact cache, so a second checkout building concurrently
blocks until the first finishes.
A replayed mutation's domain follows the log it was read out of, not the
keyspace's current routing state. Commit log replay takes the untracked
apply path and skips the routing precondition, because the commit log may
be replayed after a migration to tracked has already completed. Journal
replay stops re-deriving its route and uses the unified entry point.

Adds CommitLogReplayRoutingTest, and SSTableProvenance to classify an
sstable's origin from its coordinator log offsets and commit log intervals.
Offsets were being dropped piecemeal as compaction merged sstables, which
left no single point where an sstable became reconciled. Promotion is now
the one place offsets are cleared: a sweep promotes fully reconciled
sstables, and write-time promotion clears offsets when it sets repairedAt.

Only an incremental repair may advance mutation tracking migration, since a
full repair does not establish the reconciliation barrier promotion needs.
Adds a metric for unrepaired sstables carrying no offsets, which are
otherwise invisible.
Compacting an sstable that carries coordinator log offsets together with one
that does not produces an sstable whose offsets no longer describe its
contents, so it can never be promoted. Tracked sstables are now keyed by
their transfer set and each key gets its own CompactionStrategyHolder, so no
strategy can select across the boundary. Silos holding no transfers share
one key, and empty transfer silos are pruned.

CompactionGroup replaces the ad hoc repaired/pending checks and routes any
sstable carrying offsets to UNRECONCILED. PromoteReconciledTask promotes a
silo once its offsets are fully reconciled, replacing the standalone
promotion sweep. Offsets are cleared whenever repairedAt is set, which stops
a repaired sstable retaining offsets it can no longer act on.
Marking a corrupt sstable unrepaired exists so incremental repair notices it
and repairs it. On a tracked table that would produce an unreconcilable
sstable, so repairedAt is left unchanged and the operator is told why.

Full repair adds any missing data but cannot remove the corruption: read
resolution is last-write-wins on timestamp, so a corrupt row with a mangled
high timestamp still beats the rows repair streams in. The message says to
scrub or replace the node rather than implying a full repair is sufficient.
The commit log and the mutation journal produce positions that cannot be
compared, so a memtable's bound is only meaningful against one log. A
memtable now carries the LogDomain it accepts and refuses a write from the
other, and LogDomainBounds holds a separate bound per domain. Sealing a
bound re-reads until it stops moving, so a write racing the seal cannot land
outside it.

During migration a table takes writes from both logs at once.
SplitDomainMemtable holds one internal memtable per domain and is
substitutable for a plain memtable on reads, writes and flush. It is
installed lazily, on the first write whose domain the current memtable does
not hold, and reuses the existing internal rather than rebuilding one. A
retired generation refuses to split.

Flushing a split generation writes one sstable per domain, which keeps each
sstable's commit log interval comparable against the log it came from. The
generation boundary lives on the View, which is what knows when a generation
is retired.
Streaming reads a memtable's commit log bound to decide what the stream
covers. A split generation has two bounds that cannot be reduced to one, so
the flush-before-stream path asserts the memtable holds a single domain
rather than picking one silently.
Covers the change end to end: a keyspace altered between untracked and
tracked keeps each sstable's commit log interval comparable against the log
it came from, and a table taking writes from both logs at once flushes one
sstable per domain rather than one spanning both.

Adds TrackedIndexFlushTest, since an index of a tracked table writes through
the base table's journal and must be bounded in the same log. The index
write path says none() rather than a placeholder id.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant