All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.janusgraph.diskstorage.KeyColumnValueStoreTest Maven / Gradle / Ivy

There is a newer version: 100.3.2.1
Show newest version
// Copyright 2017 JanusGraph Authors
//
// Licensed 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.janusgraph.diskstorage;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.*;

import com.google.common.collect.ImmutableList;
import org.janusgraph.TestCategory;
import org.janusgraph.diskstorage.configuration.Configuration;
import org.janusgraph.diskstorage.keycolumnvalue.scan.ScanJob;
import org.janusgraph.diskstorage.keycolumnvalue.scan.ScanMetrics;
import org.janusgraph.diskstorage.keycolumnvalue.scan.StandardScanner;
import org.janusgraph.diskstorage.keycolumnvalue.ttl.TTLKCVSManager;
import org.janusgraph.diskstorage.util.*;

import org.janusgraph.testutil.TestGraphConfigs;
import org.junit.jupiter.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.janusgraph.diskstorage.keycolumnvalue.*;
import org.janusgraph.testutil.RandomGenerator;

import static org.junit.jupiter.api.Assertions.*;

public abstract class KeyColumnValueStoreTest extends AbstractKCVSTest {

    public static final int TRIALS = 5000;
    private final Logger log = LoggerFactory.getLogger(KeyColumnValueStoreTest.class);

    final int numKeys = 500;
    final int numColumns = 50;

    protected final String storeName = "testStore1";

    public KeyColumnValueStoreManager manager;
    public StoreTransaction tx;
    public KeyColumnValueStore store;

    @BeforeEach
    public void setUp() throws Exception {
        StoreManager m = openStorageManager();
        m.clearStorage();
        m.close();
        open();
    }

    public abstract KeyColumnValueStoreManager openStorageManager() throws BackendException;

    public void open() throws BackendException {
        manager = openStorageManager();
        store = manager.openDatabase(storeName);
        tx = startTx();
    }

    public StoreTransaction startTx() throws BackendException {
        return manager.beginTransaction(getTxConfig());
    }

    public StoreFeatures storeFeatures() {
        return manager.getFeatures();
    }

    public void clopen() throws BackendException {
        close();
        open();
    }

    @AfterEach
    public void tearDown() throws Exception {
        close();
    }

    public void close() throws BackendException {
        if (tx != null) tx.commit();
        if (null != store) store.close();
        if (null != manager) manager.close();
    }

    public void newTx() throws BackendException {
        if (tx!=null) tx.commit();
        tx = startTx();
    }

    @Test
    public void createDatabase() {
        //Just setup and shutdown
    }

    public String[][] generateValues() {
        return KeyValueStoreUtil.generateData(numKeys, numColumns);
    }

    public void loadValues(String[][] values) throws BackendException {
        KeyColumnValueStoreUtil.loadValues(store, tx, values);
    }

    public void loadValues(String[][] values, int shiftEveryNthRow, int shiftSliceLength) throws BackendException {
        KeyColumnValueStoreUtil.loadValues(store, tx, values, shiftEveryNthRow, shiftSliceLength);
    }

    /**
     * Load a bunch of key-column-values in a way that vaguely resembles a lower
     * triangular matrix.
     * 

* Iterate over key values {@code k} in the half-open long interval * {@code [offset, offset + dimension -1)}. For each {@code k}, iterate over * the column values {@code c} in the half-open integer interval * {@code [offset, k]}. *

* For each key-column coordinate specified by a {@code (k, c} pair in the *iteration, write a value one byte long with all bits set (unsigned -1 or *signed 255). * * @param dimension size of loaded data (must be positive) * @param offset offset (must be positive) * @throws BackendException unexpected failure */ public void loadLowerTriangularValues(int dimension, int offset) throws BackendException { Preconditions.checkArgument(0 < dimension); ByteBuffer val = ByteBuffer.allocate(1); val.put((byte) -1); StaticBuffer staticVal = StaticArrayBuffer.of(val); final List rowAdditions = new ArrayList<>(dimension); for (int k = 0; k < dimension; k++) { rowAdditions.clear(); ByteBuffer key = ByteBuffer.allocate(8); key.putInt(0); key.putInt(k + offset); key.flip(); StaticBuffer staticKey = StaticArrayBuffer.of(key); for (int c = 0; c <= k; c++) { ByteBuffer col = ByteBuffer.allocate(4); col.putInt(c + offset); col.flip(); StaticBuffer staticCol = StaticArrayBuffer.of(col); rowAdditions.add(StaticArrayEntry.of(staticCol, staticVal)); } store.mutate(staticKey, rowAdditions, Collections.emptyList(), tx); } } public Set deleteValues(int every) throws BackendException { final Set removed = new HashSet<>(); int counter = 0; for (int i = 0; i < numKeys; i++) { final List deletions = new ArrayList<>(); for (int j = 0; j < numColumns; j++) { counter++; if (counter % every == 0) { //remove removed.add(new KeyColumn(i, j)); deletions.add(KeyValueStoreUtil.getBuffer(j)); } } store.mutate(KeyValueStoreUtil.getBuffer(i), KeyColumnValueStore.NO_ADDITIONS, deletions, tx); } return removed; } public Set deleteKeys(int every) throws BackendException { final Set removed = new HashSet<>(); for (int i = 0; i < numKeys; i++) { if (i % every == 0) { removed.add(i); final List deletions = new ArrayList<>(); for (int j = 0; j < numColumns; j++) { deletions.add(KeyValueStoreUtil.getBuffer(j)); } store.mutate(KeyValueStoreUtil.getBuffer(i), KeyColumnValueStore.NO_ADDITIONS, deletions, tx); } } return removed; } public void checkKeys(Set removed) throws BackendException { for (int i = 0; i < numKeys; i++) { if (removed.contains(i)) { assertFalse(KCVSUtil.containsKey(store, KeyValueStoreUtil.getBuffer(i), tx)); } else { assertTrue(KCVSUtil.containsKey(store, KeyValueStoreUtil.getBuffer(i), tx)); } } } public void checkValueExistence(String[][] values) throws BackendException { checkValueExistence(values, new HashSet<>()); } public void checkValueExistence(String[][] values, Set removed) throws BackendException { for (int i = 0; i < numKeys; i++) { for (int j = 0; j < numColumns; j++) { boolean result = KCVSUtil.containsKeyColumn(store, KeyValueStoreUtil.getBuffer(i), KeyValueStoreUtil.getBuffer(j), tx); if (removed.contains(new KeyColumn(i, j))) { assertFalse(result); } else { assertTrue(result); } } } } public void checkValues(String[][] values) throws BackendException { checkValues(values, new HashSet<>()); } public void checkValues(String[][] values, Set removed) throws BackendException { for (int i = 0; i < numKeys; i++) { for (int j = 0; j < numColumns; j++) { StaticBuffer result = KCVSUtil.get(store, KeyValueStoreUtil.getBuffer(i), KeyValueStoreUtil.getBuffer(j), tx); if (removed.contains(new KeyColumn(i, j))) { assertNull(result); } else { assertEquals(values[i][j], KeyValueStoreUtil.getString(result)); } } } } @Test public void storeAndRetrieve() throws BackendException { String[][] values = generateValues(); log.debug("Loading values..."); loadValues(values); //print(values); log.debug("Checking values..."); checkValueExistence(values); checkValues(values); } //@Test public void compareStores() throws BackendException { final int keys = 1000, columns = 2000; final boolean normalMode=true; final String[][] values = new String[keys*2][]; for (int i = 0; i < keys*2; i++) { if(i%2==0) { if (normalMode) { values[i]=new String[columns + 4]; } else { values[i]=new String[4]; } } else { if (normalMode) { values[i]=new String[0]; } else { values[i]=new String[columns]; } } for (int j = 0; j < values[i].length; j++) { values[i][j] = RandomGenerator.randomString(30,35); } } log.debug("Loading values: " + keys + "x" + columns); long time = System.currentTimeMillis(); loadValues(values); clopen(); System.out.println("Loading time (ms): " + (System.currentTimeMillis() - time)); //print(values); Random r = new Random(); int trials = 500; log.debug("Reading values: " + trials + " trials"); for (int i=0; i<10;i++) { time = System.currentTimeMillis(); for (int t = 0; t < trials; t++) { int key = r.nextInt(keys)*2; assertEquals(2,store.getSlice(new KeySliceQuery(KeyValueStoreUtil.getBuffer(key), KeyValueStoreUtil.getBuffer(2002), KeyValueStoreUtil.getBuffer(2004)), tx).size()); } System.out.println("Reading time (ms): " + (System.currentTimeMillis() - time)); } } @Test public void storeAndRetrievePerformance() throws BackendException { int multiplier = 4; int keys = 50 * multiplier, columns = 200; String[][] values = KeyValueStoreUtil.generateData(keys, columns); log.debug("Loading values: " + keys + "x" + columns); long time = System.currentTimeMillis(); loadValues(values); clopen(); System.out.println("Loading time (ms): " + (System.currentTimeMillis() - time)); //print(values); Random r = new Random(); int trials = 500 * multiplier; int delta = 10; log.debug("Reading values: " + trials + " trials"); for (int i=0; i<1;i++) { time = System.currentTimeMillis(); for (int t = 0; t < trials; t++) { int key = r.nextInt(keys); int start = r.nextInt(columns - delta); store.getSlice(new KeySliceQuery(KeyValueStoreUtil.getBuffer(key), KeyValueStoreUtil.getBuffer(start), KeyValueStoreUtil.getBuffer(start + delta)), tx); } //multiQuery version // List keyList = new ArrayList(); // for (int t = 0; t < trials; t++) keyList.add(KeyValueStoreUtil.getBuffer(r.nextInt(keys))); // int start = r.nextInt(columns - delta); // store.getSlice(keyList, new SliceQuery(KeyValueStoreUtil.getBuffer(start), KeyValueStoreUtil.getBuffer(start + delta)), tx); System.out.println("Reading time (ms): " + (System.currentTimeMillis() - time)); } } @Test public void storeAndRetrieveWithClosing() throws BackendException { String[][] values = generateValues(); log.debug("Loading values..."); loadValues(values); clopen(); log.debug("Checking values..."); checkValueExistence(values); checkValues(values); } @Test public void deleteColumnsTest1() throws BackendException { String[][] values = generateValues(); log.debug("Loading values..."); loadValues(values); clopen(); Set deleted = deleteValues(7); log.debug("Checking values..."); checkValueExistence(values, deleted); checkValues(values, deleted); } @Test public void deleteColumnsTest2() throws BackendException { String[][] values = generateValues(); log.debug("Loading values..."); loadValues(values); newTx(); Set deleted = deleteValues(7); clopen(); log.debug("Checking values..."); checkValueExistence(values, deleted); checkValues(values, deleted); } @Test public void deleteKeys() throws BackendException { String[][] values = generateValues(); log.debug("Loading values..."); loadValues(values); newTx(); Set deleted = deleteKeys(11); clopen(); checkKeys(deleted); } /** * Loads a block of data where keys are longs on [idOffset, idOffset + * numKeys) and the columns are longs on [idOffset, idOffset + numColumns). * {@code idOffset} is {@link KeyValueStoreUtil#idOffset}. Note that * identical columns appear on every key. The loaded values are randomly * generated strings converted to bytes. *

* Calls the store's supported {@code getKeys} method depending on whether * it supports ordered or unordered scan. This logic is delegated to * {@link KCVSUtil#getKeys(KeyColumnValueStore, StoreFeatures, int, int, StoreTransaction)} * . That method uses all-zero and all-one buffers for the key and column * limits and retrieves every key. *

* This method does nothing and returns immediately if the store supports no * scans. */ @Test public void scanTest() throws BackendException { if (manager.getFeatures().hasScan()) { String[][] values = generateValues(); loadValues(values); KeyIterator iterator0 = KCVSUtil.getKeys(store, storeFeatures(), 8, 4, tx); verifyIterator(iterator0,numKeys); clopen(); KeyIterator iterator1 = KCVSUtil.getKeys(store, storeFeatures(), 8, 4, tx); KeyIterator iterator2 = KCVSUtil.getKeys(store, storeFeatures(), 8, 4, tx); // The idea is to open an iterator without using it // to make sure that closing a transaction will clean it up. // (important for BerkeleyJE where leaving cursors open causes exceptions) @SuppressWarnings("unused") KeyIterator iterator3 = KCVSUtil.getKeys(store, storeFeatures(), 8, 4, tx); verifyIterator(iterator1,numKeys); verifyIterator(iterator2,numKeys); } } private void verifyIterator(KeyIterator iterator, int expectedKeys) { int keys = 0; while (iterator.hasNext()) { StaticBuffer b = iterator.next(); assertTrue(b!=null && b.length()>0); keys++; RecordIterator entryRecordIterator = iterator.getEntries(); int cols = 0; while (entryRecordIterator.hasNext()) { Entry e = entryRecordIterator.next(); assertTrue(e!=null && e.length()>0); cols++; } assertEquals(1,cols); } assertEquals(expectedKeys,keys); } /** * Verify that * {@link KeyColumnValueStore#getKeys(KeyRangeQuery, StoreTransaction)} * treats the lower key bound as inclusive and the upper key bound as * exclusive. Verify that keys less than the start and greater than or equal * to the end containing matching columns are not returned. * * @throws BackendException */ @Test @Tag(TestCategory.ORDERED_KEY_STORE_TESTS) public void testOrderedGetKeysRespectsKeyLimit(TestInfo testInfo) throws BackendException { if (!manager.getFeatures().hasOrderedScan()) { log.warn("Can't test key-ordered features on incompatible store. " + "This warning could indicate reduced test coverage and " + "a broken JUnit configuration. Skipping test {}.", testInfo.getDisplayName()); return; } Preconditions.checkState(4 <= numKeys && 4 <= numColumns); final long minKey = KeyValueStoreUtil.idOffset + 1; final long maxKey = KeyValueStoreUtil.idOffset + numKeys - 2; final long expectedKeyCount = maxKey - minKey; String[][] values = generateValues(); loadValues(values); final SliceQuery columnSlice = new SliceQuery(BufferUtil.zeroBuffer(8), BufferUtil.oneBuffer(8)).setLimit(1); KeyIterator keys; keys = store.getKeys(new KeyRangeQuery(BufferUtil.getLongBuffer(minKey), BufferUtil.getLongBuffer(maxKey), columnSlice), tx); assertEquals(expectedKeyCount, KeyValueStoreUtil.count(keys)); clopen(); keys = store.getKeys(new KeyRangeQuery(BufferUtil.getLongBuffer(minKey), BufferUtil.getLongBuffer(maxKey), columnSlice), tx); assertEquals(expectedKeyCount, KeyValueStoreUtil.count(keys)); } /** * Check that {@code getKeys} methods respect column slice bounds. Uses * nearly the same data as {@link #testOrderedGetKeysRespectsKeyLimit(TestInfo)}, * except that all columns on every 10th row exceed the {@code getKeys} * slice limit. *

* For each row in this test, either all columns match the slice bounds or * all columns fall outside the slice bounds. For this reason, it could be * described as a "coarse-grained" or "simple" test of {@code getKeys}'s * column bounds checking. * * @throws BackendException */ @Test public void testGetKeysColumnSlicesSimple() throws BackendException { if (manager.getFeatures().hasScan()) { final int shiftEveryNthRows = 10; final int expectedKeyCount = numKeys / shiftEveryNthRows * (shiftEveryNthRows - 1); Preconditions.checkArgument(0 == numKeys % shiftEveryNthRows); Preconditions.checkArgument(10 < numKeys / shiftEveryNthRows); String[][] values = generateValues(); loadValues(values, shiftEveryNthRows, 4); RecordIterator i; i = KCVSUtil.getKeys(store, storeFeatures(), 8, 4, tx); assertEquals(expectedKeyCount, KeyValueStoreUtil.count(i)); clopen(); i = KCVSUtil.getKeys(store, storeFeatures(), 8, 4, tx); assertEquals(expectedKeyCount, KeyValueStoreUtil.count(i)); } } /** * Test {@code getKeys} with columns slice values chosen to trigger * potential fencepost bugs. *

* Description of data generated for and queried by this test: *

* Generate a sequence of keys as unsigned integers, starting at zero. Each * row has as many columns as the key value. The columns are generated in * the same way as the keys. This results in a sort of "lower triangular" * data space, with no values above the diagonal. * * @throws BackendException shouldn't happen * @throws IOException shouldn't happen */ @Test public void testGetKeysColumnSlicesOnLowerTriangular() throws BackendException, IOException { if (manager.getFeatures().hasScan()) { final int offset = 10; //should be greater than or equal to 1 final int size = 10; //should be greater than or equal to 4 final int midpoint = size / 2 + offset; final int upper = offset + size; final int step = 1; loadLowerTriangularValues(size, offset); boolean executed = false; if (manager.getFeatures().hasUnorderedScan()) { final Collection expected = new HashSet<>(size); for (int start = midpoint; start >= offset - step; start -= step) { for (int end = midpoint + 1; end <= upper + step; end += step) { Preconditions.checkArgument(start < end); // Set column bounds StaticBuffer startCol = BufferUtil.getIntBuffer(start); StaticBuffer endCol = BufferUtil.getIntBuffer(end); SliceQuery sq = new SliceQuery(startCol, endCol); // Compute expectation expected.clear(); for (long l = Math.max(start, offset); l < upper; l++) { expected.add(BufferUtil.getLongBuffer(l)); } // Compute actual KeyIterator i = store.getKeys(sq, tx); Collection actual = Sets.newHashSet(i); // Check log.debug("Checking bounds [{}, {}) (expect {} keys)", startCol, endCol, expected.size()); assertEquals(expected, actual); i.close(); executed = true; } } } else if (manager.getFeatures().hasOrderedScan()) { final Collection expected = new ArrayList<>(size); for (int start = midpoint; start >= offset - step; start -= step) { for (int end = midpoint + 1; end <= upper + step; end += step) { Preconditions.checkArgument(start < end); // Set column bounds StaticBuffer startCol = BufferUtil.getIntBuffer(start); StaticBuffer endCol = BufferUtil.getIntBuffer(end); SliceQuery sq = new SliceQuery(startCol, endCol); // Set key bounds StaticBuffer keyStart = BufferUtil.getLongBuffer(start); StaticBuffer keyEnd = BufferUtil.getLongBuffer(end); KeyRangeQuery krq = new KeyRangeQuery(keyStart, keyEnd, sq); // Compute expectation expected.clear(); for (long l = Math.max(start, offset); l < Math.min(upper, end); l++) { expected.add(BufferUtil.getLongBuffer(l)); } // Compute actual KeyIterator i = store.getKeys(krq, tx); Collection actual = Lists.newArrayList(i); log.debug("Checking bounds key:[{}, {}) & col:[{}, {}) (expect {} keys)", keyStart, keyEnd, startCol, endCol, expected.size()); assertEquals(expected, actual); i.close(); executed = true; } } } else { throw new UnsupportedOperationException( "Illegal store configuration: supportsScan()=true but supportsOrderedScan()=supportsUnorderedScan()=false"); } Preconditions.checkArgument(executed); } } public void checkSlice(String[][] values, Set removed, int key, int start, int end, int limit) throws BackendException { tx.rollback(); tx = startTx(); List entries; if (limit <= 0) entries = store.getSlice(new KeySliceQuery(KeyValueStoreUtil.getBuffer(key), KeyValueStoreUtil.getBuffer(start), KeyValueStoreUtil.getBuffer(end)), tx); else entries = store.getSlice(new KeySliceQuery(KeyValueStoreUtil.getBuffer(key), KeyValueStoreUtil.getBuffer(start), KeyValueStoreUtil.getBuffer(end)).setLimit(limit), tx); int pos = 0; for (int i = start; i < end; i++) { if (removed.contains(new KeyColumn(key, i))) { log.debug("Skipping deleted ({},{})", key, i); continue; } if (limit <= 0 || pos < limit) { log.debug("Checking k={}[c_start={},c_end={}](limit={}): column index={}/pos={}", key, start, end, limit, i, pos); assertTrue(entries.size() > pos); Entry entry = entries.get(pos); int col = KeyValueStoreUtil.getID(entry.getColumn()); String str = KeyValueStoreUtil.getString(entry.getValueAs(StaticBuffer.STATIC_FACTORY)); assertEquals(i, col); assertEquals(values[key][i], str); } pos++; } assertNotNull(entries); if (limit > 0 && pos > limit) assertEquals(limit, entries.size()); else assertEquals(pos, entries.size()); } @Test public void intervalTest1() throws BackendException { String[][] values = generateValues(); log.debug("Loading values..."); loadValues(values); Set deleted = Sets.newHashSet(); clopen(); checkRandomSlices(values, deleted); } @Test public void intervalTest2() throws BackendException { String[][] values = generateValues(); log.debug("Loading values..."); loadValues(values); newTx(); Set deleted = deleteValues(7); clopen(); checkRandomSlices(values, deleted); } protected void checkRandomSlices(String[][] values, Set deleted) throws BackendException { for (int t = 0; t < KeyColumnValueStoreTest.TRIALS; t++) { int key = RandomGenerator.randomInt(0, numKeys); int start = RandomGenerator.randomInt(0, numColumns); int end = RandomGenerator.randomInt(start, numColumns); int limit = RandomGenerator.randomInt(1, 30); checkSlice(values, deleted, key, start, end, limit); checkSlice(values, deleted, key, start, end, -1); } } @Test public void testConcurrentGetSlice() throws ExecutionException, InterruptedException, BackendException { testConcurrentStoreOps(false); } @Test public void testConcurrentGetSliceAndMutate() throws BackendException, ExecutionException, InterruptedException { testConcurrentStoreOps(true); } protected void testConcurrentStoreOps(boolean deletionEnabled) throws BackendException, ExecutionException, InterruptedException { // Load data fixture String[][] values = generateValues(); loadValues(values); /* * Must reopen transaction prior to deletes. * * This is due to the tx timestamps semantics. The timestamp is set once * during the lifetime of the transaction, and multiple calls to mutate will * use the same timestamp on each call. This causes deletions and additions of the * same k-v coordinates made in the same tx to conflict. On Cassandra, the * addition will win and the delete will appear to be dropped. * * The transaction open right now has already loaded the test fixtures, so any * attempt to delete some of the fixture will appear to fail if carried out in this * transaction. */ tx.commit(); tx = startTx(); // Setup executor and runnables final int NUM_THREADS = 64; ExecutorService es = Executors.newFixedThreadPool(NUM_THREADS); List tasks = new ArrayList<>(NUM_THREADS); for (int i = 0; i < NUM_THREADS; i++) { Set deleted = Sets.newHashSet(); if (!deletionEnabled) { tasks.add(new ConcurrentRandomSliceReader(values, deleted, KeyColumnValueStoreTest.TRIALS)); } else { tasks.add(new ConcurrentRandomSliceReader(values, deleted, i, KeyColumnValueStoreTest.TRIALS)); } } List> futures = new ArrayList<>(NUM_THREADS); // Execute for (Runnable r : tasks) { futures.add(es.submit(r)); } // Block to completion (and propagate any ExecutionExceptions that fall out of get) int collected = 0; for (Future f : futures) { f.get(); collected++; } assertEquals(NUM_THREADS, collected); } protected class ConcurrentRandomSliceReader implements Runnable { private final String[][] values; private final Set d; private final int startKey; private final int endKey; private final boolean deletionEnabled; private final int trials; public ConcurrentRandomSliceReader(String[][] values, Set deleted, int trials) { this.values = values; this.d = deleted; this.startKey = 0; this.endKey = values.length; this.deletionEnabled = false; this.trials = trials; } public ConcurrentRandomSliceReader(String[][] values, Set deleted, int key, int trials) { this.values = values; this.d = deleted; this.startKey = key % values.length; this.endKey = startKey + 1; this.deletionEnabled = true; this.trials = trials; } @Override public void run() { for (int t = 0; t < trials; t++) { int key = RandomGenerator.randomInt(startKey, endKey); log.debug("Random key chosen: {} (start={}, end={})", key, startKey, endKey); int start = RandomGenerator.randomInt(0, numColumns); if (start == numColumns - 1) { start = numColumns - 2; } int end = RandomGenerator.randomInt(start + 1, numColumns); int limit = RandomGenerator.randomInt(1, 30); try { if (deletionEnabled) { int delCol = RandomGenerator.randomInt(start, end); ImmutableList deletions = ImmutableList.of(KeyValueStoreUtil.getBuffer(delCol)); store.mutate(KeyValueStoreUtil.getBuffer(key), KeyColumnValueStore.NO_ADDITIONS, deletions, tx); log.debug("Deleting ({},{})", key, delCol); d.add(new KeyColumn(key, delCol)); tx.commit(); tx = startTx(); } //clopen(); checkSlice(values, d, key, start, end, limit); checkSlice(values, d, key, start, end, -1); } catch (BackendException e) { throw new RuntimeException(e); } } } } @Test public void getNonExistentKeyReturnsNull() throws Exception { assertEquals(null, KeyColumnValueStoreUtil.get(store, tx, 0, "col0")); assertEquals(null, KeyColumnValueStoreUtil.get(store, tx, 0, "col1")); } @Test public void insertingGettingAndDeletingSimpleDataWorks() throws Exception { KeyColumnValueStoreUtil.insert(store, tx, 0, "col0", "val0"); KeyColumnValueStoreUtil.insert(store, tx, 0, "col1", "val1"); tx.commit(); tx = startTx(); assertEquals("val0", KeyColumnValueStoreUtil.get(store, tx, 0, "col0")); assertEquals("val1", KeyColumnValueStoreUtil.get(store, tx, 0, "col1")); KeyColumnValueStoreUtil.delete(store, tx, 0, "col0"); KeyColumnValueStoreUtil.delete(store, tx, 0, "col1"); tx.commit(); tx = startTx(); assertEquals(null, KeyColumnValueStoreUtil.get(store, tx, 0, "col0")); assertEquals(null, KeyColumnValueStoreUtil.get(store, tx, 0, "col1")); } @Test public void getSliceRespectsColumnLimit() throws Exception { StaticBuffer key = KeyColumnValueStoreUtil.longToByteBuffer(0); final int cols = 1024; final List entries = new LinkedList<>(); for (int i = 0; i < cols; i++) { StaticBuffer col = KeyColumnValueStoreUtil.longToByteBuffer(i); entries.add(StaticArrayEntry.of(col, col)); } store.mutate(key, entries, KeyColumnValueStore.NO_DELETIONS, tx); tx.commit(); tx = startTx(); /* * When limit is greater than or equal to the matching column count , * all matching columns must be returned. */ StaticBuffer columnStart = KeyColumnValueStoreUtil.longToByteBuffer(0); StaticBuffer columnEnd = KeyColumnValueStoreUtil.longToByteBuffer(cols); List result = store.getSlice(new KeySliceQuery(key, columnStart, columnEnd).setLimit(cols), tx); assertEquals(cols, result.size()); for (int i = 0; i < result.size(); i++) { Entry src = entries.get(i); Entry dst = result.get(i); if (!src.equals(dst)) { int x = 1; } } assertEquals(entries, result); result = store.getSlice(new KeySliceQuery(key, columnStart, columnEnd).setLimit(cols + 10), tx); assertEquals(cols, result.size()); assertEquals(entries, result); /* * When limit is less the matching column count, the columns up to the * limit (ordered byte-wise) must be returned. */ result = store.getSlice(new KeySliceQuery(key, columnStart, columnEnd).setLimit(cols - 1), tx); assertEquals(cols - 1, result.size()); entries.remove(entries.size() - 1); assertEquals(entries, result); result = store.getSlice(new KeySliceQuery(key, columnStart, columnEnd).setLimit(1), tx); assertEquals(1, result.size()); final List firstEntrySingleton = Collections.singletonList(entries.get(0)); assertEquals(firstEntrySingleton, result); } @Test public void getSliceRespectsAllBoundsInclusionArguments() throws Exception { // Test case where endColumn=startColumn+1 StaticBuffer key = KeyColumnValueStoreUtil.longToByteBuffer(0); StaticBuffer columnBeforeStart = KeyColumnValueStoreUtil.longToByteBuffer(776); StaticBuffer columnStart = KeyColumnValueStoreUtil.longToByteBuffer(777); StaticBuffer columnEnd = KeyColumnValueStoreUtil.longToByteBuffer(778); StaticBuffer columnAfterEnd = KeyColumnValueStoreUtil.longToByteBuffer(779); // First insert four test Entries List entries = Arrays.asList( StaticArrayEntry.of(columnBeforeStart, columnBeforeStart), StaticArrayEntry.of(columnStart, columnStart), StaticArrayEntry.of(columnEnd, columnEnd), StaticArrayEntry.of(columnAfterEnd, columnAfterEnd)); store.mutate(key, entries, KeyColumnValueStore.NO_DELETIONS, tx); tx.commit(); // getSlice() with only start inclusive tx = startTx(); List result = store.getSlice(new KeySliceQuery(key, columnStart, columnEnd), tx); assertEquals(1, result.size()); assertEquals(777, KeyColumnValueStoreUtil.bufferToLong(result.get(0).getColumn())); } @Test public void containsKeyReturnsTrueOnExtantKey() throws Exception { StaticBuffer key1 = KeyColumnValueStoreUtil.longToByteBuffer(1); assertFalse(KCVSUtil.containsKey(store, key1, tx)); KeyColumnValueStoreUtil.insert(store, tx, 1, "c", "v"); tx.commit(); tx = startTx(); assertTrue(KCVSUtil.containsKey(store, key1, tx)); } @Test public void containsKeyReturnsFalseOnNonexistentKey() throws Exception { StaticBuffer key1 = KeyColumnValueStoreUtil.longToByteBuffer(1); assertFalse(KCVSUtil.containsKey(store, key1, tx)); } @Test public void containsKeyColumnReturnsFalseOnNonexistentInput() throws Exception { StaticBuffer key1 = KeyColumnValueStoreUtil.longToByteBuffer(1); StaticBuffer c = KeyColumnValueStoreUtil.stringToByteBuffer("c"); assertFalse(KCVSUtil.containsKeyColumn(store, key1, c, tx)); } @Test public void containsKeyColumnReturnsTrueOnExtantInput() throws Exception { KeyColumnValueStoreUtil.insert(store, tx, 1, "c", "v"); tx.commit(); tx = startTx(); StaticBuffer key1 = KeyColumnValueStoreUtil.longToByteBuffer(1); StaticBuffer c = KeyColumnValueStoreUtil.stringToByteBuffer("c"); assertTrue(KCVSUtil.containsKeyColumn(store, key1, c, tx)); } @Test public void testGetSlices() throws Exception { if (!manager.getFeatures().hasMultiQuery()) return; populateDBWith100Keys(); tx.commit(); tx = startTx(); final List keys = new ArrayList<>(100); for (int i = 1; i <= 100; i++) { keys.add(KeyColumnValueStoreUtil.longToByteBuffer(i)); } StaticBuffer start = KeyColumnValueStoreUtil.stringToByteBuffer("a"); StaticBuffer end = KeyColumnValueStoreUtil.stringToByteBuffer("d"); Map results = store.getSlice(keys, new SliceQuery(start, end), tx); assertEquals(100, results.size()); for (List entries : results.values()) { assertEquals(3, entries.size()); } } @Test @Tag(TestCategory.UNORDERED_KEY_STORE_TESTS) public void testGetKeysWithSliceQuery(TestInfo testInfo) throws Exception { if (!manager.getFeatures().hasUnorderedScan()) { log.warn("Can't test key-unordered features on incompatible store. " + "This warning could indicate reduced test coverage and " + "a broken JUnit configuration. Skipping test {}.", testInfo.getDisplayName()); return; } populateDBWith100Keys(); tx.commit(); tx = startTx(); KeyIterator keyIterator = store.getKeys( new SliceQuery(new ReadArrayBuffer("b".getBytes()), new ReadArrayBuffer("c".getBytes())), tx); examineGetKeysResults(keyIterator, 0, 100); } @Test @Tag(TestCategory.ORDERED_KEY_STORE_TESTS) public void testGetKeysWithKeyRange(TestInfo testInfo) throws Exception { if (!manager.getFeatures().hasOrderedScan()) { log.warn("Can't test ordered scans on incompatible store. " + "This warning could indicate reduced test coverage and " + "shouldn't happen in an ideal JUnit configuration. " + "Skipping test {}.", testInfo.getDisplayName()); return; } populateDBWith100Keys(); tx.commit(); tx = startTx(); KeyIterator keyIterator = store.getKeys(new KeyRangeQuery( KeyColumnValueStoreUtil.longToByteBuffer(10), // key start KeyColumnValueStoreUtil.longToByteBuffer(40), // key end new ReadArrayBuffer("b".getBytes()), // column start new ReadArrayBuffer("c".getBytes())), tx); examineGetKeysResults(keyIterator, 10, 40); } @Tag(TestCategory.BRITTLE_TESTS) @Test public void testTtl() throws Exception { if (!manager.getFeatures().hasCellTTL()) { return; } StaticBuffer key = KeyColumnValueStoreUtil.longToByteBuffer(0); int ttls[] = new int[]{0, 1, 2}; final List additions = new LinkedList<>(); for (int i = 0; i < ttls.length; i++) { StaticBuffer col = KeyColumnValueStoreUtil.longToByteBuffer(i); StaticArrayEntry entry = (StaticArrayEntry) StaticArrayEntry.of(col, col); entry.setMetaData(EntryMetaData.TTL, ttls[i]); additions.add(entry); } store.mutate(key, additions, KeyColumnValueStore.NO_DELETIONS, tx); tx.commit(); // commitTime starts just after the commit, so we won't check for expiration too early long commitTime = System.currentTimeMillis(); tx = startTx(); StaticBuffer columnStart = KeyColumnValueStoreUtil.longToByteBuffer(0); StaticBuffer columnEnd = KeyColumnValueStoreUtil.longToByteBuffer(ttls.length); List result = store.getSlice(new KeySliceQuery(key, columnStart, columnEnd).setLimit(ttls.length), tx); assertEquals(ttls.length, result.size()); // wait for one cell to expire Thread.sleep(commitTime + 1001 - System.currentTimeMillis()); // cells immediately expire upon TTL, even before rollback() result = store.getSlice(new KeySliceQuery(key, columnStart, columnEnd).setLimit(ttls.length), tx); assertEquals(ttls.length - 1, result.size()); tx.rollback(); result = store.getSlice(new KeySliceQuery(key, columnStart, columnEnd).setLimit(ttls.length), tx); assertEquals(ttls.length - 1, result.size()); Thread.sleep(commitTime + 2001 - System.currentTimeMillis()); tx.rollback(); result = store.getSlice(new KeySliceQuery(key, columnStart, columnEnd).setLimit(ttls.length), tx); assertEquals(ttls.length - 2, result.size()); // cell 0 doesn't expire due to TTL of 0 (infinite) Thread.sleep(commitTime + 4001 - System.currentTimeMillis()); tx.rollback(); result = store.getSlice(new KeySliceQuery(key, columnStart, columnEnd).setLimit(ttls.length), tx); assertEquals(ttls.length - 2, result.size()); } @Test public void testStoreTTL() throws Exception { KeyColumnValueStoreManager storeManager = manager; // TTLKCVSManager is only used when a store has cell-level TTL support but does not have store- // level TTL. // @see TTLKCVSManager if (storeManager.getFeatures().hasCellTTL() && !storeManager.getFeatures().hasStoreTTL()) { storeManager = new TTLKCVSManager(storeManager); } else if (!storeManager.getFeatures().hasStoreTTL()) { return; } assertTrue(storeManager.getFeatures().hasStoreTTL()); final TimeUnit sec = TimeUnit.SECONDS; final int storeTTLSeconds = (int)TestGraphConfigs.getTTL(sec); StoreMetaData.Container opts = new StoreMetaData.Container(); opts.put(StoreMetaData.TTL, storeTTLSeconds); KeyColumnValueStore storeWithTTL = storeManager.openDatabase("testStore_with_TTL", opts); populateDBWith100Keys(storeWithTTL); tx.commit(); tx = startTx(); final StaticBuffer key = KeyColumnValueStoreUtil.longToByteBuffer(2); StaticBuffer start = KeyColumnValueStoreUtil.stringToByteBuffer("a"); StaticBuffer end = KeyColumnValueStoreUtil.stringToByteBuffer("d"); EntryList results = storeWithTTL.getSlice(new KeySliceQuery(key, new SliceQuery(start, end)), tx); assertEquals(3, results.size()); Thread.sleep(TimeUnit.MILLISECONDS.convert((long)Math.ceil(storeTTLSeconds * 1.25), sec)); tx.commit(); tx = startTx(); results = storeWithTTL.getSlice(new KeySliceQuery(key, new SliceQuery(start, end)), tx); assertEquals(0, results.size()); // should be empty if TTL was applied properly storeWithTTL.close(); } protected void populateDBWith100Keys() throws Exception { populateDBWith100Keys(store); } protected void populateDBWith100Keys(KeyColumnValueStore store) throws Exception { Random random = new Random(); for (int i = 1; i <= 100; i++) { KeyColumnValueStoreUtil.insert(store, tx, i, "a", "v" + random.nextLong()); KeyColumnValueStoreUtil.insert(store, tx, i, "b", "v" + random.nextLong()); KeyColumnValueStoreUtil.insert(store, tx, i, "c", "v" + random.nextLong()); } } protected void examineGetKeysResults(KeyIterator keyIterator, long startKey, long endKey) { assertNotNull(keyIterator); int count = 0; int expectedNumKeys = (int) (endKey - startKey); final List existingKeys = new ArrayList<>(expectedNumKeys); for (int i = (int) (startKey == 0 ? 1 : startKey); i <= endKey; i++) { existingKeys.add(KeyColumnValueStoreUtil.longToByteBuffer(i)); } while (keyIterator.hasNext()) { StaticBuffer key = keyIterator.next(); assertNotNull(key); assertTrue(existingKeys.contains(key)); RecordIterator entries = keyIterator.getEntries(); assertNotNull(entries); int entryCount = 0; while (entries.hasNext()) { assertNotNull(entries.next()); entryCount++; } assertEquals(1, entryCount); count++; } assertEquals(expectedNumKeys, count); } @Test public void scanTestWithSimpleJob() throws Exception { int keys = 1000, columns = 40; String[][] values = KeyValueStoreUtil.generateData(keys, columns); //Make it only half the number of columns for every 2nd key for (int i = 0; i < values.length; i++) { if (i%2==0) values[i]=Arrays.copyOf(values[i],columns/2); } log.debug("Loading values: " + keys + "x" + columns); loadValues(values); clopen(); StandardScanner scanner = new StandardScanner(manager); SimpleScanJobRunner runner = (ScanJob job, Configuration jobConf, String rootNSName) -> runSimpleJob(scanner, job, jobConf); SimpleScanJob.runBasicTests(keys, columns, runner); } private ScanMetrics runSimpleJob(StandardScanner scanner, ScanJob job, Configuration jobConf) throws BackendException, ExecutionException, InterruptedException { StandardScanner.Builder jobBuilder = scanner.build(); jobBuilder.setStoreName(store.getName()); jobBuilder.setJobConfiguration(jobConf); jobBuilder.setNumProcessingThreads(2); jobBuilder.setWorkBlockSize(100); jobBuilder.setTimestampProvider(times); jobBuilder.setJob(job); return jobBuilder.execute().get(); } @Test public void testClearStorage() throws Exception { final String[][] values = generateValues(); loadValues(values); close(); manager = openStorageManagerForClearStorageTest(); assertTrue(manager.exists(), "storage should exist before clearing"); manager.clearStorage(); try { assertFalse(manager.exists(), "storage should not exist after clearing"); } catch (Exception e) { // Retry to accommodate backends (e.g. BerkeleyDB) which may require a clean manager after clearing storage manager.close(); manager = openStorageManager(); assertFalse(manager.exists(), "storage should not exist after clearing"); } } protected KeyColumnValueStoreManager openStorageManagerForClearStorageTest() throws Exception { return openStorageManager(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy