Skip to content

Commit 0aff773

Browse files
CNDB-14159: Fix DOC_LENGTHS offset calculations (#1745)
### What is the issue Addresses some of the issues discovered by riptano/cndb#14159 ### What does this PR fix and why was it fixed First I added tests that show the issue. Then, I fixed it by setting `EC` will to still have the "bug" where the ComponentMetadata will point to just after the header while ED and beyond will point to the header. There are no places we use the header other than the SystemView to get info about the length (which was wrong due to not including the header). However, these do not affect the validation of the header, so I propose that we leave them as is and just fix it header other than the SystemView to get info about the length (which was wrong due to not including the header). However, these do not affect the validation of the header, so I propose that we leave them as is and just fix it in ED.
1 parent 4030c27 commit 0aff773

File tree

6 files changed

+83
-9
lines changed

6 files changed

+83
-9
lines changed

src/java/org/apache/cassandra/index/sai/disk/v1/DocLengthsReader.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,38 @@
2222

2323
import javax.annotation.concurrent.NotThreadSafe;
2424

25+
import org.apache.cassandra.index.sai.disk.format.Version;
2526
import org.apache.cassandra.index.sai.disk.io.IndexInputReader;
2627
import org.apache.cassandra.index.sai.utils.IndexFileUtils;
2728
import org.apache.cassandra.index.sai.utils.SAICodecUtils;
2829
import org.apache.cassandra.io.util.FileHandle;
2930
import org.apache.cassandra.io.util.FileUtils;
30-
import org.apache.lucene.codecs.CodecUtil;
3131

32+
/**
33+
* Reads the component written by {@link org.apache.cassandra.index.sai.disk.v1.trie.DocLengthsWriter}.
34+
*/
3235
@NotThreadSafe
3336
public class DocLengthsReader implements Closeable
3437
{
3538
private final IndexInputReader input;
36-
private final SegmentMetadata.ComponentMetadata componentMetadata;
39+
private final long offset;
40+
private final long upperBound;
3741

38-
public DocLengthsReader(FileHandle fileHandle, SegmentMetadata.ComponentMetadata componentMetadata)
42+
public DocLengthsReader(FileHandle fileHandle, SegmentMetadata.ComponentMetadata componentMetadata, Version version)
3943
{
4044
this.input = IndexFileUtils.instance.openInput(fileHandle);
41-
this.componentMetadata = componentMetadata;
45+
// Version EC skipped the header in the doc lengths component metadata.
46+
int headerAdjustment = Version.EC.equals(version) ? 0 : SAICodecUtils.headerSize();
47+
this.offset = componentMetadata.offset + headerAdjustment;
48+
// The offset + length get you the end of the file for all relevant versions.
49+
this.upperBound = componentMetadata.offset + componentMetadata.length;
4250
}
4351

4452
public int get(int rowID) throws IOException
4553
{
4654
// Account for header size in offset calculation
47-
long position = componentMetadata.offset + (long) rowID * Integer.BYTES;
48-
if (position >= componentMetadata.offset + componentMetadata.length)
55+
long position = offset + (long) rowID * Integer.BYTES;
56+
if (position >= upperBound)
4957
return 0;
5058
input.seek(position);
5159
return input.readInt();

src/java/org/apache/cassandra/index/sai/disk/v1/InvertedIndexSearcher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public CloseableIterator<PrimaryKeyWithSortKey> orderBy(Orderer orderer, Express
209209

210210
var pkm = primaryKeyMapFactory.newPerSSTablePrimaryKeyMap();
211211
var merged = IntersectingPostingList.intersect(postingLists);
212-
var docLengthsReader = new DocLengthsReader(docLengths, docLengthsMeta);
212+
var docLengthsReader = new DocLengthsReader(docLengths, docLengthsMeta, version);
213213

214214
// Wrap the iterator with resource management
215215
var it = new AbstractIterator<BM25Utils.DocTF>() { // Anonymous class extends AbstractIterator

src/java/org/apache/cassandra/index/sai/disk/v1/trie/DocLengthsWriter.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.agrona.collections.Int2IntHashMap;
2424
import org.apache.cassandra.index.sai.disk.format.IndexComponents;
2525
import org.apache.cassandra.index.sai.disk.format.IndexComponentType;
26+
import org.apache.cassandra.index.sai.disk.format.Version;
2627
import org.apache.cassandra.index.sai.disk.io.IndexOutputWriter;
2728
import org.apache.cassandra.index.sai.utils.SAICodecUtils;
2829

@@ -33,10 +34,23 @@ public class DocLengthsWriter implements Closeable
3334
{
3435
private final IndexOutputWriter output;
3536

37+
private final long startOffset;
38+
3639
public DocLengthsWriter(IndexComponents.ForWrite components) throws IOException
3740
{
3841
this.output = components.addOrGet(IndexComponentType.DOC_LENGTHS).openOutput(true);
39-
SAICodecUtils.writeHeader(output);
42+
43+
// Version EC skipped the header in the doc lengths component metadata.
44+
if (Version.EC.equals(components.version()))
45+
{
46+
SAICodecUtils.writeHeader(output);
47+
startOffset = output.getFilePointer();
48+
}
49+
else
50+
{
51+
startOffset = output.getFilePointer();
52+
SAICodecUtils.writeHeader(output);
53+
}
4054
}
4155

4256
public void writeDocLengths(Int2IntHashMap lengths) throws IOException
@@ -65,6 +79,14 @@ public long getFilePointer()
6579
return output.getFilePointer();
6680
}
6781

82+
/**
83+
* @return file pointer where index structure begins (before header)
84+
*/
85+
public long getStartOffset()
86+
{
87+
return startOffset;
88+
}
89+
6890
@Override
6991
public void close() throws IOException
7092
{

src/java/org/apache/cassandra/index/sai/disk/v1/trie/InvertedIndexWriter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public SegmentMetadata.ComponentMetadataMap writeAll(TermsIterator terms, Int2In
103103
// Write doc lengths
104104
if (docLengthsWriter != null)
105105
{
106-
long docLengthsOffset = docLengthsWriter.getFilePointer();
106+
long docLengthsOffset = docLengthsWriter.getStartOffset();
107107
docLengthsWriter.writeDocLengths(docLengths);
108108
long docLengthsLength = docLengthsWriter.getFilePointer() - docLengthsOffset;
109109
components.put(IndexComponentType.DOC_LENGTHS, -1, docLengthsOffset, docLengthsLength);

test/unit/org/apache/cassandra/index/sai/SAITester.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import org.apache.cassandra.index.sai.disk.format.IndexComponentType;
7474
import org.apache.cassandra.index.sai.disk.format.IndexDescriptor;
7575
import org.apache.cassandra.index.sai.disk.format.Version;
76+
import org.apache.cassandra.index.sai.disk.v1.SegmentBuilder;
7677
import org.apache.cassandra.index.sai.plan.QueryController;
7778
import org.apache.cassandra.index.sai.plan.StorageAttachedIndexQueryPlan;
7879
import org.apache.cassandra.index.sai.plan.StorageAttachedIndexSearcher;
@@ -225,6 +226,14 @@ public void resetQueryOptimizationLevel() throws Throwable
225226
{
226227
// Enable the optimizer by default. If there are any tests that need to disable it, they can do so explicitly.
227228
QueryController.QUERY_OPT_LEVEL = 1;
229+
230+
}
231+
232+
@Before
233+
public void resetLastValidSegmentRowId() throws Throwable
234+
{
235+
// Don't want this setting to impact peer tests
236+
SegmentBuilder.updateLastValidSegmentRowId(-1);
228237
}
229238

230239
@After

test/unit/org/apache/cassandra/index/sai/virtual/SegmentsSystemViewTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,15 @@
2222
import java.util.Map;
2323

2424
import com.google.common.collect.ImmutableList;
25+
import org.junit.Before;
2526
import org.junit.BeforeClass;
2627
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.junit.runners.Parameterized;
30+
import java.util.Collection;
31+
import java.util.stream.Collectors;
32+
import org.apache.cassandra.index.sai.disk.format.Version;
33+
import org.apache.cassandra.index.sai.SAIUtil;
2734

2835
import org.apache.cassandra.cql3.UntypedResultSet;
2936
import org.apache.cassandra.db.ColumnFamilyStore;
@@ -40,6 +47,7 @@
4047
import org.apache.cassandra.index.sai.disk.format.IndexDescriptor;
4148
import org.apache.cassandra.index.sai.disk.v1.SegmentBuilder;
4249
import org.apache.cassandra.index.sai.disk.v1.SegmentMetadata;
50+
import org.apache.cassandra.index.sai.utils.SAICodecUtils;
4351
import org.apache.cassandra.index.sai.utils.TypeUtil;
4452
import org.apache.cassandra.io.sstable.format.SSTableReader;
4553
import org.apache.cassandra.schema.SchemaConstants;
@@ -52,8 +60,24 @@
5260
/**
5361
* Tests the virtual table exposing SSTable index segment metadata.
5462
*/
63+
@RunWith(Parameterized.class)
5564
public class SegmentsSystemViewTest extends SAITester
5665
{
66+
@Parameterized.Parameter
67+
public Version version;
68+
69+
@Parameterized.Parameters(name = "{0}")
70+
public static Collection<Object[]> data()
71+
{
72+
return Version.ALL.stream().map(v -> new Object[]{ v }).collect(Collectors.toList());
73+
}
74+
75+
@Before
76+
public void setupVersion()
77+
{
78+
SAIUtil.setCurrentVersion(version);
79+
}
80+
5781
private static final String SELECT = String.format("SELECT %s, %s, %s, %s " +
5882
"FROM %s.%s WHERE %s = '%s' AND %s = ?",
5983
SegmentsSystemView.SEGMENT_ROW_ID_OFFSET,
@@ -210,6 +234,17 @@ private HashMap<String, Long> indexFileLengths(String table) throws Exception
210234
{
211235
addComponentSizeToMap(lengths, IndexComponentType.TERMS_DATA, index.getIndexContext(), indexDescriptor);
212236
addComponentSizeToMap(lengths, IndexComponentType.POSTING_LISTS, index.getIndexContext(), indexDescriptor);
237+
if (version.onOrAfter(Version.BM25_EARLIEST))
238+
{
239+
addComponentSizeToMap(lengths, IndexComponentType.DOC_LENGTHS, index.getIndexContext(), indexDescriptor);
240+
// Version EC does not count the length of the segment header in the DOC_LENGTHS file, so
241+
// we do a special adjustment here
242+
if (version.equals(Version.EC))
243+
{
244+
var error = sstableIndex.getSegments().size() * SAICodecUtils.headerSize();
245+
lengths.computeIfPresent(IndexComponentType.DOC_LENGTHS.name(), (typeName, acc) -> acc - error);
246+
}
247+
}
213248
}
214249
else
215250
{

0 commit comments

Comments
 (0)