Skip to content

Commit bf711a0

Browse files
committed
BTreeRow.hasLiveData: avoid Cell iteration if there are no cell tombstones
patch by Dmitry Konstantinov; reviewed by Francisco Guerrero for CASSANDRA-21363
1 parent 71e8b7c commit bf711a0

4 files changed

Lines changed: 240 additions & 10 deletions

File tree

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
6.0-alpha2
2+
* BTreeRow.hasLiveData: avoid Cell iteration if there are no cell tombstones (CASSANDRA-21363)
23
* Reduce memory allocations in row merge logic (CASSANDRA-21359)
34
* Restore option to avoid hint transfer during decommission (CASSANDRA-21341)
45
* Add an offline cluster metadata tool (CASSANDRA-19151)

src/java/org/apache/cassandra/db/rows/AbstractRow.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,6 @@ public Unfiltered.Kind kind()
4444
return Unfiltered.Kind.ROW;
4545
}
4646

47-
@Override
48-
public boolean hasLiveData(long nowInSec, boolean enforceStrictLiveness)
49-
{
50-
if (primaryKeyLivenessInfo().isLive(nowInSec))
51-
return true;
52-
else if (enforceStrictLiveness)
53-
return false;
54-
return Iterables.any(cells(), cell -> cell.isLive(nowInSec));
55-
}
56-
5747
public boolean isStatic()
5848
{
5949
return clustering() == Clustering.STATIC_CLUSTERING;

src/java/org/apache/cassandra/db/rows/BTreeRow.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import com.google.common.base.Function;
3535
import com.google.common.collect.Collections2;
36+
import com.google.common.collect.Iterables;
3637
import com.google.common.collect.Iterators;
3738
import com.google.common.primitives.Ints;
3839

@@ -198,6 +199,20 @@ private static long minDeletionTime(ColumnData cd)
198199
return cd.column().isSimple() ? minDeletionTime((Cell<?>) cd) : minDeletionTime((ComplexColumnData)cd);
199200
}
200201

202+
@Override
203+
public boolean hasLiveData(long nowInSec, boolean enforceStrictLiveness)
204+
{
205+
if (primaryKeyLivenessInfo().isLive(nowInSec))
206+
return true;
207+
else if (enforceStrictLiveness)
208+
return false;
209+
// Fast path to avoid cell iteration
210+
// if there are no deleted cells then we can just check if we have at least one cell
211+
if (!hasDeletion(nowInSec))
212+
return !BTree.isEmpty(btree);
213+
return Iterables.any(cells(), cell -> cell.isLive(nowInSec));
214+
}
215+
201216
public void apply(Consumer<ColumnData> function)
202217
{
203218
BTree.apply(btree, function);
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.cassandra.db.rows;
20+
21+
import java.math.BigInteger;
22+
import java.nio.ByteBuffer;
23+
24+
import org.junit.Assert;
25+
import org.junit.Test;
26+
27+
import org.apache.cassandra.cql3.ColumnIdentifier;
28+
import org.apache.cassandra.db.Clustering;
29+
import org.apache.cassandra.db.DeletionTime;
30+
import org.apache.cassandra.db.LivenessInfo;
31+
import org.apache.cassandra.db.marshal.IntegerType;
32+
import org.apache.cassandra.db.marshal.MapType;
33+
import org.apache.cassandra.schema.ColumnMetadata;
34+
import org.apache.cassandra.schema.TableMetadata;
35+
import org.apache.cassandra.utils.ByteBufferUtil;
36+
37+
public class BTreeRowHasLiveDataTest
38+
{
39+
private static final TableMetadata metadata;
40+
private static final ColumnMetadata intColumn;
41+
private static final ColumnMetadata mapColumn;
42+
private static final Clustering<?> clusteringKey;
43+
44+
static
45+
{
46+
metadata = TableMetadata.builder("ks", "tbl")
47+
.addPartitionKeyColumn("k", IntegerType.instance)
48+
.addClusteringColumn("c", IntegerType.instance)
49+
.addRegularColumn("v", IntegerType.instance)
50+
.addRegularColumn("m", MapType.getInstance(IntegerType.instance, IntegerType.instance, true))
51+
.build();
52+
intColumn = metadata.getColumn(new ColumnIdentifier("v", false));
53+
mapColumn = metadata.getColumn(new ColumnIdentifier("m", false));
54+
clusteringKey = metadata.comparator.make(BigInteger.valueOf(1));
55+
}
56+
57+
private static final ByteBuffer KEY1 = ByteBufferUtil.bytes(1);
58+
private static final ByteBuffer KEY2 = ByteBufferUtil.bytes(2);
59+
60+
private static final ByteBuffer VAL = ByteBufferUtil.bytes(10);
61+
62+
private static Row.Builder newBuilder()
63+
{
64+
Row.Builder b = BTreeRow.unsortedBuilder();
65+
b.newRow(clusteringKey);
66+
return b;
67+
}
68+
69+
// TRIGGER: row with one live regular cell and an EMPTY primary key liveness.
70+
@Test
71+
public void rowWithOneLiveRegularCellAndEmptyPrimaryKeyLiveness_returnsTrue()
72+
{
73+
long nowInSec = nowInSec();
74+
long ts = timestampMicro(nowInSec);
75+
76+
Row.Builder b = newBuilder();
77+
// No addPrimaryKeyLivenessInfo => LivenessInfo.EMPTY
78+
b.addCell(BufferCell.live(intColumn, ts, VAL));
79+
Row row = b.build();
80+
81+
Assert.assertTrue(row.hasLiveData(nowInSec, false));
82+
}
83+
84+
// TRIGGER: same shape as above, but enforceStrictLiveness = true.
85+
@Test
86+
public void rowWithOneLiveRegularCellAndEmptyPrimaryKeyLivenessEnforcedStrictLiveness_returnsFalse()
87+
{
88+
long nowInSec = nowInSec();
89+
long ts = timestampMicro(nowInSec);
90+
91+
Row.Builder b = newBuilder();
92+
b.addCell(BufferCell.live(intColumn, ts, VAL));
93+
Row row = b.build();
94+
95+
Assert.assertFalse(row.hasLiveData(nowInSec, true));
96+
}
97+
98+
// TRIGGER: row with a TTL'd-but-not-yet-expired cell, empty PK liveness.
99+
@Test
100+
public void unexpiredTtlCell_returnsTrue()
101+
{
102+
long nowInSec = nowInSec();
103+
long ts = timestampMicro(nowInSec);
104+
105+
Row.Builder b = newBuilder();
106+
b.addCell(BufferCell.expiring(intColumn, ts, /*ttl*/ 3600, /*writeNow*/ nowInSec, VAL));
107+
Row row = b.build();
108+
109+
Assert.assertTrue(row.hasLiveData(nowInSec, false));
110+
}
111+
112+
// TRIGGER: row whose only cell is an expired TTL cell, empty PK liveness.
113+
@Test
114+
public void expiredTtlCell_returnsFalse()
115+
{
116+
long writeTime = nowInSec();
117+
long ts = timestampMicro(writeTime);
118+
long nowInSec = writeTime + 7200; // well past the TTL
119+
120+
Row.Builder b = newBuilder();
121+
b.addCell(BufferCell.expiring(intColumn, ts, /*ttl*/ 3600, /*writeNow*/ writeTime, VAL));
122+
Row row = b.build();
123+
124+
Assert.assertFalse(row.hasLiveData(nowInSec, false));
125+
}
126+
127+
// TRIGGER: row whose only cell is a tombstone, empty PK liveness.
128+
@Test
129+
public void tombstoneCell_returnsFalse()
130+
{
131+
long nowInSec = nowInSec();
132+
long ts = timestampMicro(nowInSec);
133+
134+
Row.Builder b = newBuilder();
135+
b.addCell(BufferCell.tombstone(intColumn, ts, nowInSec));
136+
Row row = b.build();
137+
138+
Assert.assertFalse(row.hasLiveData(nowInSec, false));
139+
}
140+
141+
// TRIGGER: row with a live PK liveness info (not strict).
142+
@Test
143+
public void livePK_returnsTrue()
144+
{
145+
long nowInSec = nowInSec();
146+
long ts = timestampMicro(nowInSec);
147+
148+
Row.Builder b = newBuilder();
149+
b.addPrimaryKeyLivenessInfo(LivenessInfo.create(ts, nowInSec));
150+
// No cells.
151+
Row row = b.build();
152+
153+
Assert.assertTrue(row.hasLiveData(nowInSec, false));
154+
Assert.assertTrue(row.hasLiveData(nowInSec, true));
155+
}
156+
157+
// TRIGGER: row whose only data is a row deletion (no PK liveness, no cells).
158+
@Test
159+
public void rowDeletionOnly_returnsFalse()
160+
{
161+
long nowInSec = nowInSec();
162+
long ts = timestampMicro(nowInSec);
163+
164+
Row.Builder b = newBuilder();
165+
b.addRowDeletion(new Row.Deletion(DeletionTime.build(ts, nowInSec), false));
166+
Row row = b.build();
167+
168+
Assert.assertFalse(row.hasLiveData(nowInSec, false));
169+
}
170+
171+
// TRIGGER: row with a live cell inside a complex (collection) column, empty PK liveness
172+
@Test
173+
public void liveComplexCell_returnsTrue()
174+
{
175+
long nowInSec = nowInSec();
176+
long ts = timestampMicro(nowInSec);
177+
178+
Row.Builder b = newBuilder();
179+
b.addCell(BufferCell.live(mapColumn, ts, VAL, org.apache.cassandra.db.rows.CellPath.create(KEY1)));
180+
Row row = b.build();
181+
182+
Assert.assertTrue(row.hasLiveData(nowInSec, false));
183+
}
184+
185+
// TRIGGER: row with a tombstone alongside a live regular cell.
186+
@Test
187+
public void mixedTombstoneAndLiveCell_returnsTrue()
188+
{
189+
long nowInSec = nowInSec();
190+
long ts = timestampMicro(nowInSec);
191+
192+
Row.Builder b = newBuilder();
193+
b.addCell(BufferCell.live(intColumn, ts, VAL));
194+
b.addCell(BufferCell.tombstone(mapColumn, ts, nowInSec, org.apache.cassandra.db.rows.CellPath.create(KEY1)));
195+
Row row = b.build();
196+
197+
Assert.assertTrue(row.hasLiveData(nowInSec, false));
198+
}
199+
200+
// TRIGGER: row with a tombstone alongside a live regular cell.
201+
@Test
202+
public void mixedTombstoneAndLiveComplexCells_returnsTrue()
203+
{
204+
long nowInSec = nowInSec();
205+
long ts = timestampMicro(nowInSec);
206+
207+
Row.Builder b = newBuilder();
208+
b.addCell(BufferCell.live(mapColumn, ts, VAL, org.apache.cassandra.db.rows.CellPath.create(KEY1)));
209+
b.addCell(BufferCell.tombstone(mapColumn, ts, nowInSec, org.apache.cassandra.db.rows.CellPath.create(KEY2)));
210+
Row row = b.build();
211+
212+
Assert.assertTrue(row.hasLiveData(nowInSec, false));
213+
}
214+
215+
private static long nowInSec()
216+
{
217+
return 1_000_000L;
218+
}
219+
220+
private static long timestampMicro(long nowInSec)
221+
{
222+
return nowInSec * 1_000_000L;
223+
}
224+
}

0 commit comments

Comments
 (0)