package org.apache.cassandra.io; /* * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ import static junit.framework.Assert.assertEquals; import java.io.*; import java.nio.ByteBuffer; import java.util.Collection; import java.util.concurrent.ExecutionException; import org.apache.cassandra.CleanupHelper; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamily; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.CompactionManager; import org.apache.cassandra.db.IColumn; import org.apache.cassandra.db.RowMutation; import org.apache.cassandra.db.Table; import org.apache.cassandra.db.filter.QueryPath; import org.apache.cassandra.io.sstable.IndexHelper; import org.apache.cassandra.io.sstable.SSTableReader; import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.io.util.MappedFileDataInput; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.UUIDGen; import org.junit.Test; public class LazilyCompactedRowTest extends CleanupHelper { private void assertBytes(ColumnFamilyStore cfs, int gcBefore, boolean major) throws IOException { Collection<SSTableReader> sstables = cfs.getSSTables(); CompactionIterator ci1 = new CompactionIterator(cfs, sstables, gcBefore, major); LazyCompactionIterator ci2 = new LazyCompactionIterator(cfs, sstables, gcBefore, major); while (true) { if (!ci1.hasNext()) { assert !ci2.hasNext(); break; } AbstractCompactedRow row1 = ci1.next(); AbstractCompactedRow row2 = ci2.next(); DataOutputBuffer out1 = new DataOutputBuffer(); DataOutputBuffer out2 = new DataOutputBuffer(); row1.write(out1); row2.write(out2); File tmpFile1 = File.createTempFile("lcrt1", null); File tmpFile2 = File.createTempFile("lcrt2", null); tmpFile1.deleteOnExit(); tmpFile2.deleteOnExit(); new FileOutputStream(tmpFile1).write(out1.getData()); // writing data from row1 new FileOutputStream(tmpFile2).write(out2.getData()); // writing data from row2 MappedFileDataInput in1 = new MappedFileDataInput(new FileInputStream(tmpFile1), tmpFile1.getAbsolutePath(), 0); MappedFileDataInput in2 = new MappedFileDataInput(new FileInputStream(tmpFile2), tmpFile2.getAbsolutePath(), 0); // key isn't part of what CompactedRow writes, that's done by SSTW.append // row size can differ b/c of bloom filter counts being different long rowSize1 = SSTableReader.readRowSize(in1, sstables.iterator().next().descriptor); long rowSize2 = SSTableReader.readRowSize(in2, sstables.iterator().next().descriptor); assertEquals(out1.getLength(), rowSize1 + 8); assertEquals(out2.getLength(), rowSize2 + 8); // bloom filter IndexHelper.defreezeBloomFilter(in1, rowSize1, false); IndexHelper.defreezeBloomFilter(in2, rowSize2, false); // index int indexSize1 = in1.readInt(); int indexSize2 = in2.readInt(); assertEquals(indexSize1, indexSize2); ByteBuffer bytes1 = in1.readBytes(indexSize1); ByteBuffer bytes2 = in2.readBytes(indexSize2); assert bytes1.equals(bytes2); // cf metadata ColumnFamily cf1 = ColumnFamily.create("Keyspace1", "Standard1"); ColumnFamily cf2 = ColumnFamily.create("Keyspace1", "Standard1"); ColumnFamily.serializer().deserializeFromSSTableNoColumns(cf1, in1); ColumnFamily.serializer().deserializeFromSSTableNoColumns(cf2, in2); assert cf1.getLocalDeletionTime() == cf2.getLocalDeletionTime(); assert cf1.getMarkedForDeleteAt() == cf2.getMarkedForDeleteAt(); // columns int columns = in1.readInt(); assert columns == in2.readInt(); for (int i = 0; i < columns; i++) { IColumn c1 = cf1.getColumnSerializer().deserialize(in1); IColumn c2 = cf2.getColumnSerializer().deserialize(in2); assert c1.equals(c2); } // that should be everything assert in1.available() == 0; assert in2.available() == 0; } } @Test public void testOneRow() throws IOException, ExecutionException, InterruptedException { CompactionManager.instance.disableAutoCompaction(); Table table = Table.open("Keyspace1"); ColumnFamilyStore cfs = table.getColumnFamilyStore("Standard1"); ByteBuffer key = ByteBufferUtil.bytes("k"); RowMutation rm = new RowMutation("Keyspace1", key); rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes("c")), ByteBufferUtil.EMPTY_BYTE_BUFFER, 0); rm.apply(); cfs.forceBlockingFlush(); assertBytes(cfs, Integer.MAX_VALUE, true); } @Test public void testOneRowTwoColumns() throws IOException, ExecutionException, InterruptedException { CompactionManager.instance.disableAutoCompaction(); Table table = Table.open("Keyspace1"); ColumnFamilyStore cfs = table.getColumnFamilyStore("Standard1"); ByteBuffer key = ByteBufferUtil.bytes("k"); RowMutation rm = new RowMutation("Keyspace1", key); rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes("c")), ByteBufferUtil.EMPTY_BYTE_BUFFER, 0); rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes("d")), ByteBufferUtil.EMPTY_BYTE_BUFFER, 0); rm.apply(); cfs.forceBlockingFlush(); assertBytes(cfs, Integer.MAX_VALUE, true); } @Test public void testTwoRows() throws IOException, ExecutionException, InterruptedException { CompactionManager.instance.disableAutoCompaction(); Table table = Table.open("Keyspace1"); ColumnFamilyStore cfs = table.getColumnFamilyStore("Standard1"); ByteBuffer key = ByteBufferUtil.bytes("k"); RowMutation rm = new RowMutation("Keyspace1", key); rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes("c")), ByteBufferUtil.EMPTY_BYTE_BUFFER, 0); rm.apply(); cfs.forceBlockingFlush(); rm.apply(); cfs.forceBlockingFlush(); assertBytes(cfs, Integer.MAX_VALUE, true); } @Test public void testTwoRowsTwoColumns() throws IOException, ExecutionException, InterruptedException { CompactionManager.instance.disableAutoCompaction(); Table table = Table.open("Keyspace1"); ColumnFamilyStore cfs = table.getColumnFamilyStore("Standard1"); ByteBuffer key = ByteBufferUtil.bytes("k"); RowMutation rm = new RowMutation("Keyspace1", key); rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes("c")), ByteBufferUtil.EMPTY_BYTE_BUFFER, 0); rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes("d")), ByteBufferUtil.EMPTY_BYTE_BUFFER, 0); rm.apply(); cfs.forceBlockingFlush(); rm.apply(); cfs.forceBlockingFlush(); assertBytes(cfs, Integer.MAX_VALUE, true); } @Test public void testManyRows() throws IOException, ExecutionException, InterruptedException { CompactionManager.instance.disableAutoCompaction(); Table table = Table.open("Keyspace1"); ColumnFamilyStore cfs = table.getColumnFamilyStore("Standard1"); final int ROWS_PER_SSTABLE = 10; for (int j = 0; j < (DatabaseDescriptor.getIndexInterval() * 3) / ROWS_PER_SSTABLE; j++) { for (int i = 0; i < ROWS_PER_SSTABLE; i++) { ByteBuffer key = ByteBufferUtil.bytes(String.valueOf(i % 2)); RowMutation rm = new RowMutation("Keyspace1", key); rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes(String.valueOf(i / 2))), ByteBufferUtil.EMPTY_BYTE_BUFFER, j * ROWS_PER_SSTABLE + i); rm.apply(); } cfs.forceBlockingFlush(); } assertBytes(cfs, Integer.MAX_VALUE, true); } @Test public void testTwoRowSuperColumn() throws IOException, ExecutionException, InterruptedException { CompactionManager.instance.disableAutoCompaction(); Table table = Table.open("Keyspace4"); ColumnFamilyStore cfs = table.getColumnFamilyStore("Super5"); ByteBuffer key = ByteBufferUtil.bytes("k"); RowMutation rm = new RowMutation("Keyspace4", key); ByteBuffer scKey = ByteBuffer.wrap(UUIDGen.decompose(UUIDGen.makeType1UUIDFromHost(FBUtilities.getLocalAddress()))); rm.add(new QueryPath("Super5", scKey , ByteBufferUtil.bytes("c")), ByteBufferUtil.EMPTY_BYTE_BUFFER, 0); rm.apply(); cfs.forceBlockingFlush(); rm.apply(); cfs.forceBlockingFlush(); assertBytes(cfs, Integer.MAX_VALUE, true); } private static class LazyCompactionIterator extends CompactionIterator { private final ColumnFamilyStore cfStore; public LazyCompactionIterator(ColumnFamilyStore cfStore, Iterable<SSTableReader> sstables, int gcBefore, boolean major) throws IOException { super(cfStore, sstables, gcBefore, major); this.cfStore = cfStore; } @Override protected AbstractCompactedRow getCompactedRow() { return new LazilyCompactedRow(cfStore, rows, true, Integer.MAX_VALUE, true); } } }