Skip to content

Commit 38a7a78

Browse files
Support multi-cell columns in cursor compaction
Cursor compaction now handles non-frozen collections and UDTs, supports UCS parallelize_output_shards, and applies the collection guardrails. patch by Jon Haddad; reviewed by Josh McKenzie for CASSANDRA-21463
1 parent cf8758d commit 38a7a78

67 files changed

Lines changed: 8712 additions & 2082 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGES.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
6.0-alpha3
2+
* Support multi-cell columns in cursor compaction (CASSANDRA-21463)
23
* Optimize authorization logic for BatchStatement and TransactionStatement when a single table is updated (CASSANDRA-21606)
34
* Upgrade lz4-java to 1.11.2 (CASSANDRA-21564)
45
* Encode the CQL as raw bytes to avoid utf string serialization length limitation (CASSANDRA-21599)

‎conf/cassandra_latest.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,9 @@ commitlog_segment_size: 32MiB
695695
# The default setting is legacy when the storage compatibility is set to 4 or auto otherwise.
696696
commitlog_disk_access_mode: auto
697697

698+
# Experimental. New garbage free compaction path. Does not yet support collections, counters, BTI.
699+
cursor_compaction_enabled: true
700+
698701
# Set the disk access mode for reading SSTables during compaction. The allowed values are:
699702
# - auto: inherit from disk_access_mode (default)
700703
# - direct: use direct I/O for compaction reads, bypassing the OS page cache

‎src/java/org/apache/cassandra/config/CassandraRelevantProperties.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,9 @@ public enum CassandraRelevantProperties
663663
TEST_DIFFERENTIAL_LARGEPARTITION_VALUE_PADDING("cassandra.test.differential.largepartition.value_padding", "240"),
664664
/** Seed for the randomized differential soak; defaults to the wall clock, logged per example. */
665665
TEST_DIFFERENTIAL_SEED("cassandra.test.differential.seed"),
666+
/** Column counts for the pathological wide-table differential test. */
667+
TEST_DIFFERENTIAL_WIDE_REGULARS("cassandra.test.differential.wide.regulars", "1800"),
668+
TEST_DIFFERENTIAL_WIDE_STATICS("cassandra.test.differential.wide.statics", "200"),
666669
TEST_DRIVER_CONNECTION_TIMEOUT_MS("cassandra.test.driver.connection_timeout_ms", "5000"),
667670
TEST_DRIVER_READ_TIMEOUT_MS("cassandra.test.driver.read_timeout_ms", "12000"),
668671
TEST_ENCRYPTION("cassandra.test.encryption", "false"),

‎src/java/org/apache/cassandra/db/ClusteringComparator.java‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -249,12 +249,10 @@ private static int compare(AbstractType<?>[] types, ByteBuffer c1, ByteBuffer c2
249249
int cmp = Long.compare(v2Flags, v1Flags);
250250
if (cmp != 0)
251251
{
252-
// Null ordering is type-independent: nulls sort first even on reversed
253-
// (DESC) columns, mirroring ClusteringComparator.compareComponent which
254-
// handles nulls before consulting the type. EMPTY vs VALUED, however, is
255-
// decided by the type: every base type sorts empty before values, so
256-
// ReversedType sorts empty AFTER values (it swaps operands around the
257-
// base comparison) — flip the flag-derived order for reversed columns.
252+
// Nulls are excluded because null ordering is type-independent:
253+
// compareComponent handles a null before it consults the type. Empty
254+
// against valued is not — every base type sorts empty first, so a
255+
// reversed type sorts it last, and the flag-derived order must flip.
258256
if ((v1Flags & CLUSTERING_VALUE_FLAG_NULL) == 0 && (v2Flags & CLUSTERING_VALUE_FLAG_NULL) == 0 && type.isReversed())
259257
cmp = -cmp;
260258
return cmp;

‎src/java/org/apache/cassandra/db/DeletionTime.java‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -177,18 +177,15 @@ public boolean deletes(LivenessInfo info)
177177
}
178178

179179
/**
180-
* Whether this deletion shadows a cell written at {@code timestamp}. The LIVE short-circuit is
181-
* load-bearing rather than an optimisation: {@link #deletes(long)} answers TRUE for
182-
* {@link LivenessInfo#NO_TIMESTAMP}, because Long.MIN_VALUE is both that sentinel and LIVE's
183-
* {@code markedForDeleteAt} — so a LIVE deletion would shadow a cell carrying it.
180+
* Whether this deletion shadows a cell written at {@code timestamp}.
184181
*
185-
* {@link #deletes(LivenessInfo)} has no such guard, and must not grow one. Two reasons, and the second is
186-
* the load-bearing one: for ROW liveness the answer is behaviour-neutral, since an empty row liveness has
187-
* nothing to shadow and "deleted" and "absent" coincide; and the iterator path applies that same unguarded
188-
* form to row liveness of its own accord ({@code Row.Merger.merge}, {@code BTreeRow}), so guarding it would
189-
* move the reference as well as the cursor. The guard belongs to the cell contract alone, which is why
190-
* {@link #deletes(Cell)} carries it too; that overload keeps its own copy because the check has to precede
191-
* the virtual {@code timestamp()} read it guards.
182+
* LIVE stores {@code Long.MIN_VALUE} as its {@code markedForDeleteAt}, and that is also
183+
* {@link LivenessInfo#NO_TIMESTAMP}. The LIVE check is therefore required: without it a LIVE
184+
* deletion shadows a cell that carries no timestamp.
185+
*
186+
* Do not add the same check to {@link #deletes(LivenessInfo)}. The iterator path applies the
187+
* unguarded form to row liveness ({@code Row.Merger.merge}, {@code BTreeRow}), so a guard there
188+
* would move the reference as well as the cursor.
192189
*/
193190
public boolean deletesCellAt(long timestamp)
194191
{

‎src/java/org/apache/cassandra/db/ReusableLivenessInfo.java‎

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,9 @@ public boolean isExpired()
6262
}
6363

6464
/**
65-
* The ROW contract, matching the row references: empty liveness is not live
66-
* ({@link LivenessInfo#EMPTY}, whose {@code isLive} is {@code !isEmpty()}), an
67-
* {@link #EXPIRED_LIVENESS_TTL} marker is never live ({@link LivenessInfo.ExpiredLivenessInfo}), and
68-
* otherwise an expiring liveness is live until its expiration second.
69-
*
70-
* It differs from the cell contract on three inputs, which is why cell liveness has its own type;
71-
* {@code ReusableCellLivenessInfo} carries that reading.
72-
*
73-
* The old code here read the CELL contract instead, treating an {@code EXPIRED_LIVENESS_TTL} marker
74-
* (view maintenance's PK-shadow tombstone) as live until its expiration second rather than never. The
75-
* flip is inert on this branch: the marker's {@code localExpirationTime} is stamped with
76-
* {@code nowInSec} when the view mutation is applied, and compaction only observes the row after that
77-
* mutation has been applied and flushed, so compaction-time {@code nowInSec} is never less than the
78-
* stamped one — the old read could never actually return {@code true} for it either. Exercised by
79-
* {@code MaterializedViewDifferentialCompactionTest}.
65+
* The ROW contract, matching the row references: empty liveness is not live, an
66+
* {@link #EXPIRED_LIVENESS_TTL} marker is never live, and otherwise an expiring liveness is live
67+
* until its expiration second. {@code ReusableCellLivenessInfo} carries the cell reading.
8068
*/
8169
@Override
8270
public boolean isLive(long nowInSec)

‎src/java/org/apache/cassandra/db/compaction/AbstractCompactionPipeline.java‎

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@
2727
import org.apache.cassandra.db.AbstractCompactionController;
2828
import org.apache.cassandra.db.ColumnFamilyStore;
2929
import org.apache.cassandra.db.Directories;
30+
import org.apache.cassandra.db.compaction.writers.CompactionAwareWriter;
3031
import org.apache.cassandra.db.lifecycle.ILifecycleTransaction;
3132
import org.apache.cassandra.io.sstable.format.SSTableReader;
3233
import org.apache.cassandra.utils.TimeUUID;
3334

3435
abstract class AbstractCompactionPipeline extends CompactionInfo.Holder implements AutoCloseable {
35-
// Which pipeline each compaction selected, as a delta across one compaction. Read by
36-
// CompactionPipelineCounts (test), which documents why supportability alone cannot establish it.
36+
// Pipeline selection counts, read only by CompactionPipelineCounts in the test tree.
3737
private static final LongAdder CURSOR_PIPELINES_CREATED = new LongAdder();
3838
private static final LongAdder ITERATOR_PIPELINES_CREATED = new LongAdder();
3939

@@ -66,27 +66,70 @@ static AbstractCompactionPipeline create(
6666
return new IteratorCompactionPipeline(task, type, scanners, controller, nowInSec, compactionId);
6767
}
6868

69+
final CompactionTask task;
70+
long totalKeysWritten;
71+
CompactionAwareWriter writer;
72+
73+
AbstractCompactionPipeline(CompactionTask task)
74+
{
75+
this.task = task;
76+
}
77+
78+
/**
79+
* The object that does the work. It owns the progress and the stop flag this pipeline reports,
80+
* so a subclass must not keep a second copy of either.
81+
*/
82+
abstract CompactionInfo.Holder delegate();
83+
84+
@Override
85+
public CompactionInfo getCompactionInfo()
86+
{
87+
return delegate().getCompactionInfo();
88+
}
89+
90+
@Override
91+
public boolean isGlobal()
92+
{
93+
return delegate().isGlobal();
94+
}
95+
96+
public void stop()
97+
{
98+
delegate().stop();
99+
}
100+
69101
abstract boolean processNextPartitionKey() throws IOException;
70102

71103
public abstract long[] getMergedRowCounts();
72104

73105
public abstract long getTotalSourceCQLRows();
74106

75-
public abstract long getTotalKeysWritten();
76-
77107
public abstract long getTotalBytesScanned();
78108

79-
public abstract AutoCloseable openWriterResource(ColumnFamilyStore cfs,
80-
Directories directories,
81-
ILifecycleTransaction transaction,
82-
Set<SSTableReader> nonExpiredSSTables);
83-
84109
@Override
85110
public abstract void close() throws IOException;
86111

87-
public abstract Collection<SSTableReader> finishWriting();
112+
public AutoCloseable openWriterResource(ColumnFamilyStore cfs,
113+
Directories directories,
114+
ILifecycleTransaction transaction,
115+
Set<SSTableReader> nonExpiredSSTables)
116+
{
117+
this.writer = task.getCompactionAwareWriter(cfs, directories, transaction, nonExpiredSSTables);
118+
return writer;
119+
}
88120

89-
public abstract long estimatedKeys();
121+
public Collection<SSTableReader> finishWriting()
122+
{
123+
return writer.finish();
124+
}
125+
126+
public long estimatedKeys()
127+
{
128+
return writer.estimatedKeys();
129+
}
90130

91-
public abstract void stop();
131+
public long getTotalKeysWritten()
132+
{
133+
return totalKeysWritten;
134+
}
92135
}

‎src/java/org/apache/cassandra/db/compaction/CursorCompactionPipeline.java‎

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,55 +19,21 @@
1919
package org.apache.cassandra.db.compaction;
2020

2121
import java.io.IOException;
22-
import java.util.Collection;
23-
import java.util.Set;
2422

2523
import org.apache.cassandra.db.AbstractCompactionController;
26-
import org.apache.cassandra.db.ColumnFamilyStore;
27-
import org.apache.cassandra.db.Directories;
28-
import org.apache.cassandra.db.compaction.writers.CompactionAwareWriter;
29-
import org.apache.cassandra.db.lifecycle.ILifecycleTransaction;
30-
import org.apache.cassandra.io.sstable.format.SSTableReader;
3124
import org.apache.cassandra.utils.TimeUUID;
3225

3326
class CursorCompactionPipeline extends AbstractCompactionPipeline {
3427
final CursorCompactor cursorCompactor;
35-
final CompactionTask task;
36-
long totalKeysWritten;
37-
CompactionAwareWriter writer;
3828

3929
CursorCompactionPipeline(CompactionTask task, OperationType type, AbstractCompactionStrategy.ScannerList scanners, AbstractCompactionController controller, long nowInSec, TimeUUID compactionId) {
40-
this.task = task;
30+
super(task);
4131
cursorCompactor = new CursorCompactor(type, scanners.scanners, controller, nowInSec, compactionId);
4232
}
4333

44-
public AutoCloseable openWriterResource(ColumnFamilyStore cfs,
45-
Directories directories,
46-
ILifecycleTransaction transaction,
47-
Set<SSTableReader> nonExpiredSSTables) {
48-
this.writer = task.getCompactionAwareWriter(cfs, directories, transaction, nonExpiredSSTables);
49-
return writer;
50-
}
51-
52-
53-
@Override
54-
public Collection<SSTableReader> finishWriting() {
55-
return writer.finish();
56-
}
57-
58-
@Override
59-
public long estimatedKeys() {
60-
return writer.estimatedKeys();
61-
}
62-
6334
@Override
64-
public CompactionInfo getCompactionInfo() {
65-
return cursorCompactor.getCompactionInfo();
66-
}
67-
68-
@Override
69-
public boolean isGlobal() {
70-
return cursorCompactor.isGlobal();
35+
CompactionInfo.Holder delegate() {
36+
return cursorCompactor;
7137
}
7238

7339
@Override
@@ -90,11 +56,6 @@ public long getTotalSourceCQLRows() {
9056
return cursorCompactor.getTotalSourceCQLRows();
9157
}
9258

93-
@Override
94-
public long getTotalKeysWritten() {
95-
return totalKeysWritten;
96-
}
97-
9859
@Override
9960
public long getTotalBytesScanned() {
10061
return cursorCompactor.getTotalBytesScanned();
@@ -104,9 +65,4 @@ public long getTotalBytesScanned() {
10465
public void close() throws IOException {
10566
cursorCompactor.close();
10667
}
107-
108-
@Override
109-
public void stop() {
110-
cursorCompactor.stop();
111-
}
11268
}

0 commit comments

Comments
 (0)