mirror of https://github.com/apache/cassandra
226 lines
8.4 KiB
Java
226 lines
8.4 KiB
Java
/*
|
|
* 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.
|
|
*/
|
|
|
|
package org.apache.cassandra.cql3;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Random;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.Future;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
import org.hamcrest.Matchers;
|
|
import org.junit.After;
|
|
import org.junit.Assert;
|
|
import org.junit.Test;
|
|
|
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertTrue;
|
|
|
|
public class EarlyOpenCompactionTest extends CQLTester
|
|
{
|
|
private static final int NUM_PARTITIONS = 1000;
|
|
private static final int NUM_ROWS_PER_PARTITION = 100;
|
|
private static final int VALUE_SIZE = 1000; // ~1KB per row
|
|
private static final int VERIFICATION_THREADS = 4;
|
|
|
|
private final AtomicBoolean stopVerification = new AtomicBoolean(false);
|
|
private final AtomicInteger verificationErrors = new AtomicInteger(0);
|
|
private final Random random = new Random();
|
|
private ExecutorService executor;
|
|
|
|
@After
|
|
public void cleanupAfter() throws Throwable
|
|
{
|
|
stopVerification.set(true);
|
|
if (executor != null)
|
|
{
|
|
executor.shutdownNow();
|
|
executor.awaitTermination(1, TimeUnit.MINUTES);
|
|
}
|
|
DatabaseDescriptor.setSSTablePreemptiveOpenIntervalInMiB(50);
|
|
}
|
|
|
|
@Test
|
|
public void testEarlyOpenDuringCompaction() throws Throwable
|
|
{
|
|
// Create a table with a simple schema
|
|
createTable("CREATE TABLE %s (" +
|
|
"pk int, " +
|
|
"ck int, " +
|
|
"data text, " +
|
|
"PRIMARY KEY (pk, ck)" +
|
|
")");
|
|
|
|
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
|
disableCompaction();
|
|
|
|
// Insert data to create multiple SSTables
|
|
System.out.println("Inserting test data...");
|
|
for (int i = 0; i < NUM_PARTITIONS; i++)
|
|
{
|
|
for (int j = 0; j < NUM_ROWS_PER_PARTITION; j++)
|
|
{
|
|
String value = randomString(VALUE_SIZE);
|
|
execute("INSERT INTO %s (pk, ck, data) VALUES (?, ?, ?)", i, j, value);
|
|
}
|
|
|
|
// Flush from time to time to get 10 sstables
|
|
if (i > 0 && i % Math.max(1, NUM_PARTITIONS / 10) == 0)
|
|
{
|
|
flush();
|
|
}
|
|
}
|
|
|
|
// Final flush to ensure all data is written
|
|
flush();
|
|
|
|
// Verify we have multiple SSTables
|
|
int sstableCount = cfs.getLiveSSTables().size();
|
|
assertTrue("Expected multiple SSTables, got: " + sstableCount, sstableCount > 1);
|
|
|
|
// Start verification threads
|
|
System.out.println("Starting verification threads...");
|
|
executor = Executors.newFixedThreadPool(VERIFICATION_THREADS);
|
|
List<Future<?>> futures = new ArrayList<>();
|
|
|
|
for (int i = 0; i < VERIFICATION_THREADS; i++)
|
|
{
|
|
futures.add(executor.submit(new VerificationTask()));
|
|
}
|
|
|
|
// Wait a bit to ensure verification is running
|
|
Thread.sleep(1000);
|
|
|
|
// Set early open interval to 1MiB to trigger early open during compaction
|
|
System.out.println("Setting early open interval to 1MiB...");
|
|
DatabaseDescriptor.setSSTablePreemptiveOpenIntervalInMiB(1);
|
|
// Slow down compaction to give the verifier time to fail.
|
|
DatabaseDescriptor.setCompactionThroughputMebibytesPerSec(10);
|
|
|
|
// Trigger compaction and await its completion
|
|
System.out.println("Starting compaction...");
|
|
cfs.enableAutoCompaction(true);
|
|
|
|
// Let verification run for a while during and after compaction
|
|
System.out.println("Verifying data during and after compaction...");
|
|
Thread.sleep(1000);
|
|
|
|
// Stop verification
|
|
stopVerification.set(true);
|
|
|
|
// Wait for verification to complete
|
|
for (Future<?> future : futures)
|
|
{
|
|
try
|
|
{
|
|
future.get(10, TimeUnit.SECONDS);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
System.err.println("Verification task failed: " + e);
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
// Verify no errors occurred during verification
|
|
int errors = verificationErrors.get();
|
|
assertEquals("Found " + errors + " verification errors. Check logs for details.", 0, errors);
|
|
|
|
System.out.println("Test completed successfully");
|
|
}
|
|
|
|
private class VerificationTask implements Runnable
|
|
{
|
|
@Override
|
|
public void run()
|
|
{
|
|
try
|
|
{
|
|
Random localRandom = new Random(Thread.currentThread().getId());
|
|
|
|
while (!stopVerification.get() && !Thread.currentThread().isInterrupted())
|
|
{
|
|
// Randomly choose between point query and partition range query
|
|
if (localRandom.nextBoolean())
|
|
{
|
|
// Point query
|
|
int pk = localRandom.nextInt(NUM_PARTITIONS * 110 / 100); // 10% chance outside
|
|
int ck = localRandom.nextInt(NUM_ROWS_PER_PARTITION * 110 / 100); // 10% chance outside
|
|
|
|
try
|
|
{
|
|
Assert.assertEquals(pk < NUM_PARTITIONS && ck < NUM_ROWS_PER_PARTITION ? 1 : 0,
|
|
execute("SELECT data FROM %s WHERE pk = ? AND ck = ?", pk, ck).size());
|
|
}
|
|
catch (Throwable t)
|
|
{
|
|
verificationErrors.incrementAndGet();
|
|
System.err.println("Point query failed for pk=" + pk + ", ck=" + ck + ": " + t);
|
|
t.printStackTrace();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Partition range query
|
|
int pk = localRandom.nextInt(NUM_PARTITIONS);
|
|
|
|
try
|
|
{
|
|
Assert.assertThat(execute("SELECT data FROM %s WHERE token(pk) <= token(?) AND token(pk) >= token(?)", pk, pk).size(),
|
|
Matchers.greaterThanOrEqualTo(NUM_ROWS_PER_PARTITION));
|
|
}
|
|
catch (Throwable t)
|
|
{
|
|
verificationErrors.incrementAndGet();
|
|
System.err.println("Range query failed for pk in (" + pk + ", " + (pk + 1) + ", " + (pk + 2) + "): " + t);
|
|
t.printStackTrace();
|
|
}
|
|
}
|
|
|
|
// Add a small delay to prevent overwhelming the system
|
|
Thread.yield();
|
|
}
|
|
}
|
|
catch (Throwable t)
|
|
{
|
|
verificationErrors.incrementAndGet();
|
|
System.err.println("Verification task failed: " + t);
|
|
t.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
private String randomString(int length)
|
|
{
|
|
StringBuilder sb = new StringBuilder(length);
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
sb.append((char)('a' + random.nextInt(26)));
|
|
}
|
|
return sb.toString();
|
|
}
|
|
}
|