add versioning test
This commit is contained in:
parent
af30947b66
commit
2ad8a60532
|
@ -18,8 +18,20 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.versioning;
|
package org.elasticsearch.versioning;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.apache.lucene.util.LuceneTestCase.Slow;
|
||||||
|
import org.apache.lucene.util.TestUtil;
|
||||||
import org.elasticsearch.action.bulk.BulkResponse;
|
import org.elasticsearch.action.bulk.BulkResponse;
|
||||||
import org.elasticsearch.action.delete.DeleteResponse;
|
import org.elasticsearch.action.delete.DeleteResponse;
|
||||||
|
import org.elasticsearch.action.index.IndexRequest;
|
||||||
import org.elasticsearch.action.index.IndexResponse;
|
import org.elasticsearch.action.index.IndexResponse;
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
import org.elasticsearch.common.lucene.uid.Versions;
|
import org.elasticsearch.common.lucene.uid.Versions;
|
||||||
|
@ -29,11 +41,10 @@ import org.elasticsearch.index.engine.VersionConflictEngineException;
|
||||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
|
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertThrows;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertThrows;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -336,4 +347,303 @@ public class SimpleVersioningTests extends ElasticsearchIntegrationTest {
|
||||||
IndexResponse indexResponse = bulkResponse.getItems()[0].getResponse();
|
IndexResponse indexResponse = bulkResponse.getItems()[0].getResponse();
|
||||||
assertThat(indexResponse.getVersion(), equalTo(1l));
|
assertThat(indexResponse.getVersion(), equalTo(1l));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Poached from Lucene's TestIDVersionPostingsFormat:
|
||||||
|
|
||||||
|
private interface IDSource {
|
||||||
|
String next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDSource getRandomIDs() {
|
||||||
|
IDSource ids;
|
||||||
|
final Random random = getRandom();
|
||||||
|
switch (random.nextInt(6)) {
|
||||||
|
case 0:
|
||||||
|
// random simple
|
||||||
|
if (VERBOSE) {
|
||||||
|
System.out.println("TEST: use random simple ids");
|
||||||
|
}
|
||||||
|
ids = new IDSource() {
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
return TestUtil.randomSimpleString(random);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// random realistic unicode
|
||||||
|
if (VERBOSE) {
|
||||||
|
System.out.println("TEST: use random realistic unicode ids");
|
||||||
|
}
|
||||||
|
ids = new IDSource() {
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
return TestUtil.randomRealisticUnicodeString(random);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// sequential
|
||||||
|
if (VERBOSE) {
|
||||||
|
System.out.println("TEST: use seuquential ids");
|
||||||
|
}
|
||||||
|
ids = new IDSource() {
|
||||||
|
int upto;
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
return Integer.toString(upto++);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// zero-pad sequential
|
||||||
|
if (VERBOSE) {
|
||||||
|
System.out.println("TEST: use zero-pad seuquential ids");
|
||||||
|
}
|
||||||
|
ids = new IDSource() {
|
||||||
|
final int radix = TestUtil.nextInt(random, Character.MIN_RADIX, Character.MAX_RADIX);
|
||||||
|
final String zeroPad = String.format(Locale.ROOT, "%0" + TestUtil.nextInt(random, 4, 20) + "d", 0);
|
||||||
|
int upto;
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
String s = Integer.toString(upto++);
|
||||||
|
return zeroPad.substring(zeroPad.length() - s.length()) + s;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
// random long
|
||||||
|
if (VERBOSE) {
|
||||||
|
System.out.println("TEST: use random long ids");
|
||||||
|
}
|
||||||
|
ids = new IDSource() {
|
||||||
|
final int radix = TestUtil.nextInt(random, Character.MIN_RADIX, Character.MAX_RADIX);
|
||||||
|
int upto;
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
return Long.toString(random.nextLong() & 0x3ffffffffffffffL, radix);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
// zero-pad random long
|
||||||
|
if (VERBOSE) {
|
||||||
|
System.out.println("TEST: use zero-pad random long ids");
|
||||||
|
}
|
||||||
|
ids = new IDSource() {
|
||||||
|
final int radix = TestUtil.nextInt(random, Character.MIN_RADIX, Character.MAX_RADIX);
|
||||||
|
final String zeroPad = String.format(Locale.ROOT, "%015d", 0);
|
||||||
|
int upto;
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
return Long.toString(random.nextLong() & 0x3ffffffffffffffL, radix);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class IDAndVersion {
|
||||||
|
public String id;
|
||||||
|
public long version;
|
||||||
|
public boolean delete;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Slow
|
||||||
|
public void testRandomIDsAndVersions() throws Exception {
|
||||||
|
createIndex("test");
|
||||||
|
ensureGreen();
|
||||||
|
|
||||||
|
// TODO: sometimes use _bulk API
|
||||||
|
// TODO: test non-aborting exceptions (Rob suggested field where positions overflow)
|
||||||
|
|
||||||
|
// TODO: not great we don't test deletes GC here:
|
||||||
|
|
||||||
|
// We test deletes, but can't rely on wall-clock delete GC:
|
||||||
|
HashMap<String,Object> newSettings = new HashMap<>();
|
||||||
|
newSettings.put("index.gc_deletes", "1000000h");
|
||||||
|
client().admin().indices().prepareUpdateSettings("test").setSettings(newSettings).execute().actionGet();
|
||||||
|
|
||||||
|
Random random = getRandom();
|
||||||
|
|
||||||
|
// Generate random IDs:
|
||||||
|
IDSource idSource = getRandomIDs();
|
||||||
|
Set<String> idsSet = new HashSet<>();
|
||||||
|
|
||||||
|
String idPrefix;
|
||||||
|
if (randomBoolean()) {
|
||||||
|
idPrefix = "";
|
||||||
|
} else {
|
||||||
|
idPrefix = TestUtil.randomSimpleString(random);
|
||||||
|
if (VERBOSE) {
|
||||||
|
System.out.println("TEST: use id prefix: " + idPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int numIDs;
|
||||||
|
if (isNightly()) {
|
||||||
|
numIDs = scaledRandomIntBetween(10000, 20000);
|
||||||
|
} else {
|
||||||
|
numIDs = scaledRandomIntBetween(500, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (idsSet.size() < numIDs) {
|
||||||
|
idsSet.add(idPrefix + idSource.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] ids = idsSet.toArray(new String[numIDs]);
|
||||||
|
|
||||||
|
boolean useMonotonicVersion = randomBoolean();
|
||||||
|
|
||||||
|
// Attach random versions to them:
|
||||||
|
long version = 0;
|
||||||
|
final IDAndVersion[] idVersions = new IDAndVersion[TestUtil.nextInt(random, numIDs/2, numIDs*(isNightly() ? 10 : 2))];
|
||||||
|
final Map<String,IDAndVersion> truth = new HashMap<>();
|
||||||
|
|
||||||
|
if (VERBOSE) {
|
||||||
|
System.out.println("TEST: use " + numIDs + " ids; " + idVersions.length + " operations");
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0;i<idVersions.length;i++) {
|
||||||
|
|
||||||
|
if (useMonotonicVersion) {
|
||||||
|
version += TestUtil.nextInt(random, 1, 10);
|
||||||
|
} else {
|
||||||
|
version = random.nextLong() & 0x3fffffffffffffffL;
|
||||||
|
}
|
||||||
|
|
||||||
|
idVersions[i] = new IDAndVersion();
|
||||||
|
idVersions[i].id = ids[random.nextInt(numIDs)];
|
||||||
|
idVersions[i].version = version;
|
||||||
|
// 20% of the time we delete:
|
||||||
|
idVersions[i].delete = random.nextInt(5) == 2;
|
||||||
|
IDAndVersion curVersion = truth.get(idVersions[i].id);
|
||||||
|
if (curVersion == null || idVersions[i].version > curVersion.version) {
|
||||||
|
// Save highest version per id:
|
||||||
|
truth.put(idVersions[i].id, idVersions[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle
|
||||||
|
for(int i = idVersions.length - 1; i > 0; i--) {
|
||||||
|
int index = random.nextInt(i + 1);
|
||||||
|
IDAndVersion x = idVersions[index];
|
||||||
|
idVersions[index] = idVersions[i];
|
||||||
|
idVersions[i] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VERBOSE) {
|
||||||
|
for(IDAndVersion idVersion : idVersions) {
|
||||||
|
System.out.println("id=" + idVersion.id + " version=" + idVersion.version + " delete?=" + idVersion.delete + " truth?=" + (truth.get(idVersion.id) == idVersion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final AtomicInteger upto = new AtomicInteger();
|
||||||
|
final CountDownLatch startingGun = new CountDownLatch(1);
|
||||||
|
Thread[] threads = new Thread[TestUtil.nextInt(random, 1, isNightly() ? 20 : 5)];
|
||||||
|
for(int i=0;i<threads.length;i++) {
|
||||||
|
threads[i] = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
//final Random threadRandom = RandomizedContext.current().getRandom();
|
||||||
|
final Random threadRandom = getRandom();
|
||||||
|
startingGun.await();
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
// TODO: sometimes us bulk:
|
||||||
|
|
||||||
|
int index = upto.getAndIncrement();
|
||||||
|
if (index >= idVersions.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (VERBOSE && index % 100 == 0) {
|
||||||
|
System.out.println(Thread.currentThread().getName() + ": index=" + index);
|
||||||
|
}
|
||||||
|
|
||||||
|
String id = idVersions[index].id;
|
||||||
|
long version = idVersions[index].version;
|
||||||
|
if (idVersions[index].delete) {
|
||||||
|
try {
|
||||||
|
client().prepareDelete("test", "type", id)
|
||||||
|
.setVersion(version)
|
||||||
|
.setVersionType(VersionType.EXTERNAL).execute().actionGet();
|
||||||
|
} catch (VersionConflictEngineException vcee) {
|
||||||
|
// OK: our version is too old
|
||||||
|
assertThat(version, lessThanOrEqualTo(truth.get(id).version));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int x=0;x<2;x++) {
|
||||||
|
// Try create first:
|
||||||
|
|
||||||
|
IndexRequest.OpType op;
|
||||||
|
if (x == 0) {
|
||||||
|
op = IndexRequest.OpType.CREATE;
|
||||||
|
} else {
|
||||||
|
op = IndexRequest.OpType.INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// index document
|
||||||
|
try {
|
||||||
|
client().prepareIndex("test", "type", id)
|
||||||
|
.setSource("foo", "bar")
|
||||||
|
.setOpType(op)
|
||||||
|
.setVersion(version)
|
||||||
|
.setVersionType(VersionType.EXTERNAL).execute().actionGet();
|
||||||
|
break;
|
||||||
|
} catch (DocumentAlreadyExistsException vcee) {
|
||||||
|
if (x == 0) {
|
||||||
|
// OK: id was already index by another thread, now use index:
|
||||||
|
} else {
|
||||||
|
// Should not happen with op=INDEX:
|
||||||
|
throw vcee;
|
||||||
|
}
|
||||||
|
} catch (VersionConflictEngineException vcee) {
|
||||||
|
// OK: our version is too old
|
||||||
|
assertThat(version, lessThanOrEqualTo(truth.get(id).version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threadRandom.nextInt(10) == 7) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
if (threadRandom.nextInt(20) == 7) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
threads[i].start();
|
||||||
|
}
|
||||||
|
|
||||||
|
startingGun.countDown();
|
||||||
|
for(Thread thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify against truth:
|
||||||
|
for(String id : ids) {
|
||||||
|
long expected;
|
||||||
|
IDAndVersion idVersion = truth.get(id);
|
||||||
|
if (idVersion != null && idVersion.delete == false) {
|
||||||
|
expected = idVersion.version;
|
||||||
|
} else {
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
assertThat("id=" + id + " idVersion=" + idVersion, client().prepareGet("test", "type", id).execute().actionGet().getVersion(), equalTo(expected));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue