/*
* 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.ignite.internal.processors.cache;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.cache.Cache;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cache.CachePeekMode;
import org.apache.ignite.cache.eviction.lru.LruEvictionPolicy;
import org.apache.ignite.cache.query.ScanQuery;
import org.apache.ignite.cache.query.SqlFieldsQuery;
import org.apache.ignite.cache.query.SqlQuery;
import org.apache.ignite.cache.query.annotations.QuerySqlField;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.IgniteKernal;
import org.apache.ignite.internal.processors.cache.query.GridCacheQueryManager;
import org.apache.ignite.internal.processors.query.GridQueryProcessor;
import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing;
import org.apache.ignite.internal.util.typedef.CAX;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
import static org.apache.ignite.cache.CacheMode.PARTITIONED;
import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
/**
* Multi-threaded tests for cache queries.
*/
@SuppressWarnings("StatementWithEmptyBody")
public class IgniteCacheQueryMultiThreadedSelfTest extends GridCommonAbstractTest {
/** */
private static final boolean TEST_INFO = true;
/** Number of test grids (nodes). Should not be less than 2. */
private static final int GRID_CNT = 3;
/** */
private static TcpDiscoveryIpFinder ipFinder = new TcpDiscoveryVmIpFinder(true);
/** */
private static AtomicInteger idxSwapCnt = new AtomicInteger();
/** */
private static AtomicInteger idxUnswapCnt = new AtomicInteger();
/** */
private static final long DURATION = 30 * 1000;
/** Don't start grid by default. */
public IgniteCacheQueryMultiThreadedSelfTest() {
super(false);
}
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
TcpDiscoverySpi disco = new TcpDiscoverySpi();
disco.setIpFinder(ipFinder);
cfg.setDiscoverySpi(disco);
cfg.setCacheConfiguration(cacheConfiguration());
return cfg;
}
/**
* @return Cache configuration.
*/
protected CacheConfiguration cacheConfiguration() {
CacheConfiguration<?,?> cacheCfg = defaultCacheConfiguration();
cacheCfg.setCacheMode(PARTITIONED);
cacheCfg.setAtomicityMode(TRANSACTIONAL);
cacheCfg.setWriteSynchronizationMode(FULL_SYNC);
cacheCfg.setBackups(1);
LruEvictionPolicy plc = null;
if (evictsEnabled()) {
plc = new LruEvictionPolicy();
plc.setMaxSize(100);
}
cacheCfg.setEvictionPolicy(plc);
cacheCfg.setOnheapCacheEnabled(plc != null);
return cacheCfg;
}
/**
* @param clsK Key class.
* @param clsV Value class.
* @return Cache.
*/
protected <K, V> IgniteCache<K, V> cache(Class<K> clsK, Class<V> clsV) {
return jcache(grid(0), cacheConfiguration(), clsK, clsV);
}
/** {@inheritDoc} */
@Override protected long getTestTimeout() {
return DURATION + 60_000;
}
/** @return {@code true} If evictions enabled. */
protected boolean evictsEnabled() {
return false;
}
/** {@inheritDoc} */
@Override protected void beforeTest() throws Exception {
super.beforeTest();
// Clean up all caches.
for (int i = 0; i < GRID_CNT; i++) {
IgniteCache<Object, Object> c = grid(i).cache(DEFAULT_CACHE_NAME);
assertEquals(0, c.size());
}
}
/** {@inheritDoc} */
@Override protected void beforeTestsStarted() throws Exception {
assert GRID_CNT >= 2 : "Constant GRID_CNT must be greater than or equal to 2.";
startGridsMultiThreaded(GRID_CNT);
}
/** {@inheritDoc} */
@Override protected void afterTestsStopped() throws Exception {
stopAllGrids();
}
/** {@inheritDoc} */
@Override protected void afterTest() throws Exception {
super.afterTest();
// Clean up all caches.
for (int i = 0; i < GRID_CNT; i++) {
for(String cacheName : grid(i).cacheNames()) {
IgniteCache<Object, Object> c = grid(i).cache(cacheName);
c.removeAll();
// Fix for tests where mapping was removed at primary node
// but was not removed at others.
// removeAll() removes mapping only when it presents at a primary node.
// To remove all mappings used force remove by key.
if (c.size() > 0) {
for (Cache.Entry<Object, Object> e : c.localEntries())
c.remove(e.getKey());
}
}
}
U.sleep(5000);
for (int i = 0; i < GRID_CNT; i++) {
for(String cacheName : grid(i).cacheNames()) {
IgniteCache<Object, Object> c = grid(i).cache(cacheName);
assertEquals(0, c.size(CachePeekMode.OFFHEAP));
assertEquals(0, c.size(CachePeekMode.PRIMARY));
assertEquals(0, c.size());
}
}
}
/** {@inheritDoc} */
@Override protected void info(String msg) {
if (TEST_INFO)
super.info(msg);
}
/**
* @param entries Entries.
* @param g Grid.
* @return Affinity nodes.
*/
private Set<UUID> affinityNodes(Iterable<Cache.Entry<Integer, Integer>> entries, Ignite g) {
Set<UUID> nodes = new HashSet<>();
for (Cache.Entry<Integer, Integer> entry : entries)
nodes.add(g.affinity(DEFAULT_CACHE_NAME).mapKeyToPrimaryAndBackups(entry.getKey()).iterator().next().id());
return nodes;
}
/**
* JUnit.
*
* @throws Exception If failed.
*/
@SuppressWarnings({"TooBroadScope"})
public void testMultiThreadedSwapUnswapString() throws Exception {
int threadCnt = 50;
final int keyCnt = 2000;
final int valCnt = 10000;
final Ignite g = grid(0);
// Put test values into cache.
final IgniteCache<Integer, String> c = cache(Integer.class, String.class);
assertEquals(0, g.cache(DEFAULT_CACHE_NAME).localSize());
assertEquals(0, c.query(new SqlQuery(String.class, "1 = 1")).getAll().size());
Random rnd = new Random();
for (int i = 0; i < keyCnt; i += 1 + rnd.nextInt(3)) {
c.put(i, String.valueOf(rnd.nextInt(valCnt)));
if (evictsEnabled() && rnd.nextBoolean())
c.localEvict(Arrays.asList(i));
}
final AtomicBoolean done = new AtomicBoolean();
IgniteInternalFuture<?> fut = multithreadedAsync(new CAX() {
@Override public void applyx() throws IgniteCheckedException {
Random rnd = new Random();
while (!done.get()) {
switch (rnd.nextInt(5)) {
case 0:
c.put(rnd.nextInt(keyCnt), String.valueOf(rnd.nextInt(valCnt)));
break;
case 1:
if (evictsEnabled())
c.localEvict(Arrays.asList(rnd.nextInt(keyCnt)));
break;
case 2:
c.remove(rnd.nextInt(keyCnt));
break;
case 3:
c.get(rnd.nextInt(keyCnt));
break;
case 4:
int from = rnd.nextInt(valCnt);
c.query(new SqlQuery(String.class, "_val between ? and ?").setArgs(
String.valueOf(from), String.valueOf(from + 250))).getAll();
}
}
}
}, threadCnt);
Thread.sleep(DURATION);
done.set(true);
fut.get();
}
/**
* JUnit.
*
* @throws Exception If failed.
*/
@SuppressWarnings({"TooBroadScope"})
public void testMultiThreadedSwapUnswapLong() throws Exception {
int threadCnt = 50;
final int keyCnt = 2000;
final int valCnt = 10000;
final Ignite g = grid(0);
// Put test values into cache.
final IgniteCache<Integer, Long> c = cache(Integer.class, Long.class);
assertEquals(0, g.cache(DEFAULT_CACHE_NAME).localSize());
assertEquals(0, c.query(new SqlQuery(Long.class, "1 = 1")).getAll().size());
Random rnd = new Random();
for (int i = 0; i < keyCnt; i += 1 + rnd.nextInt(3)) {
c.put(i, (long)rnd.nextInt(valCnt));
if (evictsEnabled() && rnd.nextBoolean())
c.localEvict(Arrays.asList(i));
}
final AtomicBoolean done = new AtomicBoolean();
IgniteInternalFuture<?> fut = multithreadedAsync(new CAX() {
@Override public void applyx() throws IgniteCheckedException {
Random rnd = new Random();
while (!done.get()) {
int key = rnd.nextInt(keyCnt);
switch (rnd.nextInt(5)) {
case 0:
c.put(key, (long)rnd.nextInt(valCnt));
break;
case 1:
if (evictsEnabled())
c.localEvict(Arrays.asList(key));
break;
case 2:
c.remove(key);
break;
case 3:
c.get(key);
break;
case 4:
int from = rnd.nextInt(valCnt);
c.query(new SqlQuery(Long.class, "_val between ? and ?").setArgs(from, from + 250)).getAll();
}
}
}
}, threadCnt);
Thread.sleep(DURATION);
done.set(true);
fut.get();
}
/**
* JUnit.
*
* @throws Exception If failed.
*/
@SuppressWarnings({"TooBroadScope"})
public void _testMultiThreadedSwapUnswapLongString() throws Exception {
fail("http://atlassian.gridgain.com/jira/browse/GG-11216");
int threadCnt = 50;
final int keyCnt = 2000;
final int valCnt = 10000;
final Ignite g = grid(0);
// Put test values into cache.
final IgniteCache<Integer, Object> c = cache(Integer.class, Object.class);
assertEquals(0, g.cache(DEFAULT_CACHE_NAME).size());
assertEquals(0, c.query(new SqlQuery(Object.class, "1 = 1")).getAll().size());
Random rnd = new Random();
for (int i = 0; i < keyCnt; i += 1 + rnd.nextInt(3)) {
c.put(i, rnd.nextBoolean() ? (long)rnd.nextInt(valCnt) : String.valueOf(rnd.nextInt(valCnt)));
if (evictsEnabled() && rnd.nextBoolean())
c.localEvict(Arrays.asList(i));
}
final AtomicBoolean done = new AtomicBoolean();
IgniteInternalFuture<?> fut = multithreadedAsync(new CAX() {
@Override public void applyx() throws IgniteCheckedException {
Random rnd = new Random();
while (!done.get()) {
int key = rnd.nextInt(keyCnt);
switch (rnd.nextInt(5)) {
case 0:
c.put(key, rnd.nextBoolean() ? (long)rnd.nextInt(valCnt) :
String.valueOf(rnd.nextInt(valCnt)));
break;
case 1:
if (evictsEnabled())
c.localEvict(Arrays.asList(key));
break;
case 2:
c.remove(key);
break;
case 3:
c.get(key);
break;
case 4:
int from = rnd.nextInt(valCnt);
c.query(new SqlQuery(Object.class, "_val between ? and ?").setArgs(from, from + 250))
.getAll();
}
}
}
}, threadCnt);
Thread.sleep(DURATION);
done.set(true);
fut.get();
}
/**
* @throws Exception If failed.
*/
@SuppressWarnings({"TooBroadScope"})
public void testMultiThreadedSwapUnswapObject() throws Exception {
int threadCnt = 50;
final int keyCnt = 4000;
final int valCnt = 10000;
final Ignite g = grid(0);
// Put test values into cache.
final IgniteCache<Integer, TestValue> c = cache(Integer.class, TestValue.class);
assertEquals(0, g.cache(DEFAULT_CACHE_NAME).localSize());
assertEquals(0, c.query(new SqlQuery(TestValue.class, "1 = 1")).getAll().size());
Random rnd = new Random();
for (int i = 0; i < keyCnt; i += 1 + rnd.nextInt(3)) {
c.put(i, new TestValue(rnd.nextInt(valCnt)));
if (evictsEnabled() && rnd.nextBoolean())
c.localEvict(Arrays.asList(i));
}
final AtomicBoolean done = new AtomicBoolean();
IgniteInternalFuture<?> fut = multithreadedAsync(new CAX() {
@Override public void applyx() throws IgniteCheckedException {
Random rnd = new Random();
while (!done.get()) {
int key = rnd.nextInt(keyCnt);
switch (rnd.nextInt(5)) {
case 0:
c.put(key, new TestValue(rnd.nextInt(valCnt)));
break;
case 1:
if (evictsEnabled())
c.localEvict(Arrays.asList(key));
break;
case 2:
c.remove(key);
break;
case 3:
c.get(key);
break;
case 4:
int from = rnd.nextInt(valCnt);
c.query(new SqlQuery(TestValue.class, "TestValue.val between ? and ?")
.setArgs(from, from + 250)).getAll();
}
}
}
}, threadCnt);
Thread.sleep(DURATION);
done.set(true);
fut.get();
}
/**
* JUnit.
*
* @throws Exception If failed.
*/
@SuppressWarnings({"TooBroadScope"})
public void testMultiThreadedSameQuery() throws Exception {
int threadCnt = 50;
final int keyCnt = 10;
final int logMod = 5000;
final Ignite g = grid(0);
// Put test values into cache.
final IgniteCache<Integer, Integer> c = cache(Integer.class, Integer.class);
for (int i = 0; i < keyCnt; i++) {
c.put(i, i);
c.localEvict(Arrays.asList(i));
}
final AtomicInteger cnt = new AtomicInteger();
final AtomicBoolean done = new AtomicBoolean();
IgniteInternalFuture<?> fut = multithreadedAsync(
new CAX() {
@Override public void applyx() throws IgniteCheckedException {
int iter = 0;
while (!done.get() && !Thread.currentThread().isInterrupted()) {
iter++;
Collection<Cache.Entry<Integer, Integer>> entries =
c.query(new SqlQuery(Integer.class, "_val >= 0")).getAll();
assert entries != null;
assertEquals("Query results [entries=" + entries + ", aff=" + affinityNodes(entries, g) +
", iteration=" + iter + ']', keyCnt, entries.size());
if (cnt.incrementAndGet() % logMod == 0) {
GridCacheQueryManager<Object, Object> qryMgr =
((IgniteKernal)g).internalCache(c.getName()).context().queries();
assert qryMgr != null;
qryMgr.printMemoryStats();
}
}
}
}, threadCnt);
Thread.sleep(DURATION);
info("Finishing test...");
done.set(true);
fut.get();
}
/**
* JUnit.
*
* @throws Exception If failed.
*/
@SuppressWarnings({"TooBroadScope"})
public void testMultiThreadedNewQueries() throws Exception {
int threadCnt = 50;
final int keyCnt = 10;
final int logMod = 5000;
final Ignite g = grid(0);
// Put test values into cache.
final IgniteCache<Integer, Integer> c = cache(Integer.class, Integer.class);
for (int i = 0; i < keyCnt; i++) {
c.put(i, i);
c.localEvict(Arrays.asList(i));
}
final AtomicInteger cnt = new AtomicInteger();
final AtomicBoolean done = new AtomicBoolean();
IgniteInternalFuture<?> fut = multithreadedAsync(new CAX() {
@Override public void applyx() throws IgniteCheckedException {
int iter = 0;
while (!done.get() && !Thread.currentThread().isInterrupted()) {
iter++;
Collection<Cache.Entry<Integer, Integer>> entries =
c.query(new SqlQuery(Integer.class, "_val >= 0")).getAll();
assert entries != null;
assertEquals("Entries count is not as expected on iteration: " + iter, keyCnt, entries.size());
if (cnt.incrementAndGet() % logMod == 0) {
GridCacheQueryManager<Object, Object> qryMgr =
((IgniteKernal)g).internalCache(c.getName()).context().queries();
assert qryMgr != null;
qryMgr.printMemoryStats();
}
}
}
}, threadCnt);
Thread.sleep(DURATION);
done.set(true);
fut.get();
}
/**
* JUnit.
*
* @throws Exception If failed.
*/
@SuppressWarnings({"TooBroadScope"})
public void testMultiThreadedScanQuery() throws Exception {
int threadCnt = 50;
final int keyCnt = 500;
final int logMod = 5000;
final Ignite g = grid(0);
// Put test values into cache.
final IgniteCache<Integer, Integer> c = cache(Integer.class, Integer.class);
for (int i = 0; i < keyCnt; i++)
c.put(i, i);
final AtomicInteger cnt = new AtomicInteger();
final AtomicBoolean done = new AtomicBoolean();
IgniteInternalFuture<?> fut = multithreadedAsync(
new CAX() {
@Override public void applyx() throws IgniteCheckedException {
int iter = 0;
while (!done.get() && !Thread.currentThread().isInterrupted()) {
iter++;
// Scan query.
Collection<Cache.Entry<Integer, Integer>> entries =
c.query(new ScanQuery<Integer, Integer>()).getAll();
assert entries != null;
assertEquals("Entries count is not as expected on iteration: " + iter, keyCnt, entries.size());
if (cnt.incrementAndGet() % logMod == 0) {
GridCacheQueryManager<Object, Object> qryMgr =
((IgniteKernal)g).internalCache(c.getName()).context().queries();
assert qryMgr != null;
qryMgr.printMemoryStats();
}
}
}
}, threadCnt);
Thread.sleep(DURATION);
done.set(true);
fut.get();
}
/**
* SqlFieldsQuery paging mechanics stress test
*
* @throws Exception If failed.
*/
@SuppressWarnings({"TooBroadScope"})
public void testMultiThreadedSqlFieldsQuery() throws Throwable {
int threadCnt = 16;
final int keyCnt = 1100; // set resultSet size bigger than page size
final int logMod = 5000;
final Ignite g = grid(0);
// Put test values into cache.
final IgniteCache<Integer, TestValue> c = cache(Integer.class, TestValue.class);
for (int i = 0; i < keyCnt; i++)
c.put(i, new TestValue(i));
final AtomicInteger cnt = new AtomicInteger();
final AtomicBoolean done = new AtomicBoolean();
IgniteInternalFuture<?> fut = multithreadedAsync(
new CAX() {
@Override public void applyx() throws IgniteCheckedException {
int iter = 0;
while (!done.get() && !Thread.currentThread().isInterrupted()) {
iter++;
List<List<?>> entries =
c.query(new SqlFieldsQuery("SELECT * from TestValue").setPageSize(100)).getAll();
assert entries != null;
assertEquals("Entries count is not as expected on iteration: " + iter, keyCnt, entries.size());
if (cnt.incrementAndGet() % logMod == 0) {
GridCacheQueryManager<Object, Object> qryMgr =
((IgniteKernal)g).internalCache(DEFAULT_CACHE_NAME).context().queries();
assert qryMgr != null;
qryMgr.printMemoryStats();
}
}
}
}, threadCnt);
Thread.sleep(DURATION);
done.set(true);
fut.get();
}
/**
* Test value.
*/
private static class TestValue implements Serializable {
/** Value. */
@QuerySqlField(index = true)
private int val;
/**
* @param val Value.
*/
private TestValue(int val) {
this.val = val;
}
/**
* @return Value.
*/
public int value() {
return val;
}
}
}