SimpleVersioningIT.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. /*
  2. * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
  3. * or more contributor license agreements. Licensed under the Elastic License
  4. * 2.0 and the Server Side Public License, v 1; you may not use this file except
  5. * in compliance with, at your election, the Elastic License 2.0 or the Server
  6. * Side Public License, v 1.
  7. */
  8. package org.elasticsearch.versioning;
  9. import org.apache.lucene.tests.util.TestUtil;
  10. import org.elasticsearch.action.ActionResponse;
  11. import org.elasticsearch.action.DocWriteRequest;
  12. import org.elasticsearch.action.DocWriteResponse;
  13. import org.elasticsearch.action.RequestBuilder;
  14. import org.elasticsearch.action.bulk.BulkResponse;
  15. import org.elasticsearch.action.delete.DeleteResponse;
  16. import org.elasticsearch.action.get.GetResponse;
  17. import org.elasticsearch.action.index.IndexResponse;
  18. import org.elasticsearch.cluster.metadata.IndexMetadata;
  19. import org.elasticsearch.common.lucene.uid.Versions;
  20. import org.elasticsearch.common.settings.Settings;
  21. import org.elasticsearch.core.Strings;
  22. import org.elasticsearch.index.VersionType;
  23. import org.elasticsearch.index.engine.VersionConflictEngineException;
  24. import org.elasticsearch.test.ESIntegTestCase;
  25. import java.util.HashMap;
  26. import java.util.HashSet;
  27. import java.util.Map;
  28. import java.util.Random;
  29. import java.util.Set;
  30. import java.util.concurrent.atomic.AtomicInteger;
  31. import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
  32. import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFutureThrows;
  33. import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
  34. import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertResponse;
  35. import static org.hamcrest.Matchers.equalTo;
  36. import static org.hamcrest.Matchers.lessThanOrEqualTo;
  37. public class SimpleVersioningIT extends ESIntegTestCase {
  38. public void testExternalVersioningInitialDelete() throws Exception {
  39. createIndex("test");
  40. ensureGreen();
  41. // Note - external version doesn't throw version conflicts on deletes of non existent records.
  42. // This is different from internal versioning
  43. DeleteResponse deleteResponse = client().prepareDelete("test", "1").setVersion(17).setVersionType(VersionType.EXTERNAL).get();
  44. assertEquals(DocWriteResponse.Result.NOT_FOUND, deleteResponse.getResult());
  45. // this should conflict with the delete command transaction which told us that the object was deleted at version 17.
  46. assertFutureThrows(
  47. prepareIndex("test").setId("1").setSource("field1", "value1_1").setVersion(13).setVersionType(VersionType.EXTERNAL).execute(),
  48. VersionConflictEngineException.class
  49. );
  50. DocWriteResponse indexResponse = prepareIndex("test").setId("1")
  51. .setSource("field1", "value1_1")
  52. .setVersion(18)
  53. .setVersionType(VersionType.EXTERNAL)
  54. .get();
  55. assertThat(indexResponse.getVersion(), equalTo(18L));
  56. }
  57. public void testExternalGTE() throws Exception {
  58. createIndex("test");
  59. DocWriteResponse indexResponse = prepareIndex("test").setId("1")
  60. .setSource("field1", "value1_1")
  61. .setVersion(12)
  62. .setVersionType(VersionType.EXTERNAL_GTE)
  63. .get();
  64. assertThat(indexResponse.getVersion(), equalTo(12L));
  65. indexResponse = prepareIndex("test").setId("1")
  66. .setSource("field1", "value1_2")
  67. .setVersion(12)
  68. .setVersionType(VersionType.EXTERNAL_GTE)
  69. .get();
  70. assertThat(indexResponse.getVersion(), equalTo(12L));
  71. indexResponse = prepareIndex("test").setId("1")
  72. .setSource("field1", "value1_2")
  73. .setVersion(14)
  74. .setVersionType(VersionType.EXTERNAL_GTE)
  75. .get();
  76. assertThat(indexResponse.getVersion(), equalTo(14L));
  77. RequestBuilder<?, ?> builder1 = prepareIndex("test").setId("1")
  78. .setSource("field1", "value1_1")
  79. .setVersion(13)
  80. .setVersionType(VersionType.EXTERNAL_GTE);
  81. expectThrows(VersionConflictEngineException.class, builder1);
  82. client().admin().indices().prepareRefresh().get();
  83. if (randomBoolean()) {
  84. refresh();
  85. }
  86. for (int i = 0; i < 10; i++) {
  87. assertThat(client().prepareGet("test", "1").get().getVersion(), equalTo(14L));
  88. }
  89. // deleting with a lower version fails.
  90. RequestBuilder<?, ?> builder = client().prepareDelete("test", "1").setVersion(2).setVersionType(VersionType.EXTERNAL_GTE);
  91. expectThrows(VersionConflictEngineException.class, builder);
  92. // Delete with a higher or equal version deletes all versions up to the given one.
  93. long v = randomIntBetween(14, 17);
  94. DeleteResponse deleteResponse = client().prepareDelete("test", "1").setVersion(v).setVersionType(VersionType.EXTERNAL_GTE).get();
  95. assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult());
  96. assertThat(deleteResponse.getVersion(), equalTo(v));
  97. // Deleting with a lower version keeps on failing after a delete.
  98. assertFutureThrows(
  99. client().prepareDelete("test", "1").setVersion(2).setVersionType(VersionType.EXTERNAL_GTE).execute(),
  100. VersionConflictEngineException.class
  101. );
  102. // But delete with a higher version is OK.
  103. deleteResponse = client().prepareDelete("test", "1").setVersion(18).setVersionType(VersionType.EXTERNAL_GTE).get();
  104. assertEquals(DocWriteResponse.Result.NOT_FOUND, deleteResponse.getResult());
  105. assertThat(deleteResponse.getVersion(), equalTo(18L));
  106. }
  107. public void testExternalVersioning() throws Exception {
  108. createIndex("test");
  109. ensureGreen();
  110. DocWriteResponse indexResponse = prepareIndex("test").setId("1")
  111. .setSource("field1", "value1_1")
  112. .setVersion(12)
  113. .setVersionType(VersionType.EXTERNAL)
  114. .get();
  115. assertThat(indexResponse.getVersion(), equalTo(12L));
  116. indexResponse = prepareIndex("test").setId("1")
  117. .setSource("field1", "value1_1")
  118. .setVersion(14)
  119. .setVersionType(VersionType.EXTERNAL)
  120. .get();
  121. assertThat(indexResponse.getVersion(), equalTo(14L));
  122. assertFutureThrows(
  123. prepareIndex("test").setId("1").setSource("field1", "value1_1").setVersion(13).setVersionType(VersionType.EXTERNAL).execute(),
  124. VersionConflictEngineException.class
  125. );
  126. if (randomBoolean()) {
  127. refresh();
  128. }
  129. for (int i = 0; i < 10; i++) {
  130. assertThat(client().prepareGet("test", "1").get().getVersion(), equalTo(14L));
  131. }
  132. // deleting with a lower version fails.
  133. assertFutureThrows(
  134. client().prepareDelete("test", "1").setVersion(2).setVersionType(VersionType.EXTERNAL).execute(),
  135. VersionConflictEngineException.class
  136. );
  137. // Delete with a higher version deletes all versions up to the given one.
  138. DeleteResponse deleteResponse = client().prepareDelete("test", "1").setVersion(17).setVersionType(VersionType.EXTERNAL).get();
  139. assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult());
  140. assertThat(deleteResponse.getVersion(), equalTo(17L));
  141. // Deleting with a lower version keeps on failing after a delete.
  142. assertFutureThrows(
  143. client().prepareDelete("test", "1").setVersion(2).setVersionType(VersionType.EXTERNAL).execute(),
  144. VersionConflictEngineException.class
  145. );
  146. // But delete with a higher version is OK.
  147. deleteResponse = client().prepareDelete("test", "1").setVersion(18).setVersionType(VersionType.EXTERNAL).get();
  148. assertEquals(DocWriteResponse.Result.NOT_FOUND, deleteResponse.getResult());
  149. assertThat(deleteResponse.getVersion(), equalTo(18L));
  150. // TODO: This behavior breaks rest api returning http status 201
  151. // good news is that it this is only the case until deletes GC kicks in.
  152. indexResponse = prepareIndex("test").setId("1")
  153. .setSource("field1", "value1_1")
  154. .setVersion(19)
  155. .setVersionType(VersionType.EXTERNAL)
  156. .get();
  157. assertThat(indexResponse.getVersion(), equalTo(19L));
  158. deleteResponse = client().prepareDelete("test", "1").setVersion(20).setVersionType(VersionType.EXTERNAL).get();
  159. assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult());
  160. assertThat(deleteResponse.getVersion(), equalTo(20L));
  161. // Make sure that the next delete will be GC. Note we do it on the index settings so it will be cleaned up
  162. updateIndexSettings(Settings.builder().put("index.gc_deletes", -1), "test");
  163. Thread.sleep(300); // gc works based on estimated sampled time. Give it a chance...
  164. // And now we have previous version return -1
  165. indexResponse = prepareIndex("test").setId("1")
  166. .setSource("field1", "value1_1")
  167. .setVersion(20)
  168. .setVersionType(VersionType.EXTERNAL)
  169. .get();
  170. assertThat(indexResponse.getVersion(), equalTo(20L));
  171. }
  172. public void testRequireUnitsOnUpdateSettings() throws Exception {
  173. createIndex("test");
  174. ensureGreen();
  175. HashMap<String, Object> newSettings = new HashMap<>();
  176. newSettings.put("index.gc_deletes", "42");
  177. try {
  178. client().admin().indices().prepareUpdateSettings("test").setSettings(newSettings).get();
  179. fail("did not hit expected exception");
  180. } catch (IllegalArgumentException iae) {
  181. // expected
  182. assertTrue(
  183. iae.getMessage()
  184. .contains("failed to parse setting [index.gc_deletes] with value [42] as a time value: unit is missing or unrecognized")
  185. );
  186. }
  187. }
  188. public void testCompareAndSetInitialDelete() throws Exception {
  189. createIndex("test");
  190. ensureGreen();
  191. assertFutureThrows(
  192. client().prepareDelete("test", "1").setIfSeqNo(17).setIfPrimaryTerm(10).execute(),
  193. VersionConflictEngineException.class
  194. );
  195. DocWriteResponse indexResponse = prepareIndex("test").setId("1").setSource("field1", "value1_1").setCreate(true).get();
  196. assertThat(indexResponse.getVersion(), equalTo(1L));
  197. }
  198. public void testCompareAndSet() {
  199. createIndex("test");
  200. ensureGreen();
  201. DocWriteResponse indexResponse = prepareIndex("test").setId("1").setSource("field1", "value1_1").get();
  202. assertThat(indexResponse.getSeqNo(), equalTo(0L));
  203. assertThat(indexResponse.getPrimaryTerm(), equalTo(1L));
  204. indexResponse = prepareIndex("test").setId("1").setSource("field1", "value1_2").setIfSeqNo(0L).setIfPrimaryTerm(1).get();
  205. assertThat(indexResponse.getSeqNo(), equalTo(1L));
  206. assertThat(indexResponse.getPrimaryTerm(), equalTo(1L));
  207. assertFutureThrows(
  208. prepareIndex("test").setId("1").setSource("field1", "value1_1").setIfSeqNo(10).setIfPrimaryTerm(1).execute(),
  209. VersionConflictEngineException.class
  210. );
  211. assertFutureThrows(
  212. prepareIndex("test").setId("1").setSource("field1", "value1_1").setIfSeqNo(10).setIfPrimaryTerm(2).execute(),
  213. VersionConflictEngineException.class
  214. );
  215. assertFutureThrows(
  216. prepareIndex("test").setId("1").setSource("field1", "value1_1").setIfSeqNo(1).setIfPrimaryTerm(2).execute(),
  217. VersionConflictEngineException.class
  218. );
  219. RequestBuilder<?, ?> builder6 = client().prepareDelete("test", "1").setIfSeqNo(10).setIfPrimaryTerm(1);
  220. expectThrows(VersionConflictEngineException.class, builder6);
  221. RequestBuilder<?, ?> builder5 = client().prepareDelete("test", "1").setIfSeqNo(10).setIfPrimaryTerm(2);
  222. expectThrows(VersionConflictEngineException.class, builder5);
  223. RequestBuilder<?, ?> builder4 = client().prepareDelete("test", "1").setIfSeqNo(1).setIfPrimaryTerm(2);
  224. expectThrows(VersionConflictEngineException.class, builder4);
  225. client().admin().indices().prepareRefresh().get();
  226. for (int i = 0; i < 10; i++) {
  227. final GetResponse response = client().prepareGet("test", "1").get();
  228. assertThat(response.getSeqNo(), equalTo(1L));
  229. assertThat(response.getPrimaryTerm(), equalTo(1L));
  230. }
  231. // search with versioning
  232. for (int i = 0; i < 10; i++) {
  233. // TODO: ADD SEQ NO!
  234. assertResponse(
  235. prepareSearch().setQuery(matchAllQuery()).setVersion(true),
  236. response -> assertThat(response.getHits().getAt(0).getVersion(), equalTo(2L))
  237. );
  238. }
  239. // search without versioning
  240. for (int i = 0; i < 10; i++) {
  241. assertResponse(
  242. prepareSearch().setQuery(matchAllQuery()),
  243. response -> assertThat(response.getHits().getAt(0).getVersion(), equalTo(Versions.NOT_FOUND))
  244. );
  245. }
  246. DeleteResponse deleteResponse = client().prepareDelete("test", "1").setIfSeqNo(1).setIfPrimaryTerm(1).get();
  247. assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult());
  248. assertThat(deleteResponse.getSeqNo(), equalTo(2L));
  249. assertThat(deleteResponse.getPrimaryTerm(), equalTo(1L));
  250. RequestBuilder<?, ?> builder3 = client().prepareDelete("test", "1").setIfSeqNo(1).setIfPrimaryTerm(1);
  251. expectThrows(VersionConflictEngineException.class, builder3);
  252. RequestBuilder<?, ?> builder2 = client().prepareDelete("test", "1").setIfSeqNo(3).setIfPrimaryTerm(12);
  253. expectThrows(VersionConflictEngineException.class, builder2);
  254. RequestBuilder<?, ?> builder1 = client().prepareDelete("test", "1").setIfSeqNo(1).setIfPrimaryTerm(2);
  255. expectThrows(VersionConflictEngineException.class, builder1);
  256. // the doc is deleted. Even when we hit the deleted seqNo, a conditional delete should fail.
  257. RequestBuilder<?, ?> builder = client().prepareDelete("test", "1").setIfSeqNo(2).setIfPrimaryTerm(1);
  258. expectThrows(VersionConflictEngineException.class, builder);
  259. }
  260. public void testSimpleVersioningWithFlush() throws Exception {
  261. createIndex("test");
  262. ensureGreen();
  263. DocWriteResponse indexResponse = prepareIndex("test").setId("1").setSource("field1", "value1_1").get();
  264. assertThat(indexResponse.getSeqNo(), equalTo(0L));
  265. client().admin().indices().prepareFlush().get();
  266. indexResponse = prepareIndex("test").setId("1").setSource("field1", "value1_2").setIfSeqNo(0).setIfPrimaryTerm(1).get();
  267. assertThat(indexResponse.getSeqNo(), equalTo(1L));
  268. client().admin().indices().prepareFlush().get();
  269. RequestBuilder<?, ?> builder2 = prepareIndex("test").setId("1").setSource("field1", "value1_1").setIfSeqNo(0).setIfPrimaryTerm(1);
  270. expectThrows(VersionConflictEngineException.class, builder2);
  271. RequestBuilder<?, ?> builder1 = prepareIndex("test").setId("1").setCreate(true).setSource("field1", "value1_1");
  272. expectThrows(VersionConflictEngineException.class, builder1);
  273. RequestBuilder<?, ?> builder = client().prepareDelete("test", "1").setIfSeqNo(0).setIfPrimaryTerm(1);
  274. expectThrows(VersionConflictEngineException.class, builder);
  275. for (int i = 0; i < 10; i++) {
  276. assertThat(client().prepareGet("test", "1").get().getVersion(), equalTo(2L));
  277. }
  278. client().admin().indices().prepareRefresh().get();
  279. for (int i = 0; i < 10; i++) {
  280. assertResponse(prepareSearch().setQuery(matchAllQuery()).setVersion(true).seqNoAndPrimaryTerm(true), response -> {
  281. assertHitCount(response, 1);
  282. assertThat(response.getHits().getAt(0).getVersion(), equalTo(2L));
  283. assertThat(response.getHits().getAt(0).getSeqNo(), equalTo(1L));
  284. });
  285. }
  286. }
  287. public void testVersioningWithBulk() {
  288. createIndex("test");
  289. ensureGreen();
  290. BulkResponse bulkResponse = client().prepareBulk().add(prepareIndex("test").setId("1").setSource("field1", "value1_1")).get();
  291. assertThat(bulkResponse.hasFailures(), equalTo(false));
  292. assertThat(bulkResponse.getItems().length, equalTo(1));
  293. IndexResponse indexResponse = bulkResponse.getItems()[0].getResponse();
  294. assertThat(indexResponse.getVersion(), equalTo(1L));
  295. }
  296. // Poached from Lucene's TestIDVersionPostingsFormat:
  297. private interface IDSource {
  298. String next();
  299. }
  300. private IDSource getRandomIDs() {
  301. final Random random = random();
  302. return switch (random.nextInt(6)) {
  303. case 0 -> {
  304. // random simple
  305. logger.info("--> use random simple ids");
  306. yield new IDSource() {
  307. @Override
  308. public String next() {
  309. return TestUtil.randomSimpleString(random, 1, 10);
  310. }
  311. };
  312. }
  313. case 1 -> {
  314. // random realistic unicode
  315. logger.info("--> use random realistic unicode ids");
  316. yield new IDSource() {
  317. @Override
  318. public String next() {
  319. return TestUtil.randomRealisticUnicodeString(random, 1, 20);
  320. }
  321. };
  322. }
  323. case 2 -> {
  324. // sequential
  325. logger.info("--> use sequential ids");
  326. yield new IDSource() {
  327. int upto;
  328. @Override
  329. public String next() {
  330. return Integer.toString(upto++);
  331. }
  332. };
  333. }
  334. case 3 -> {
  335. // zero-pad sequential
  336. logger.info("--> use zero-padded sequential ids");
  337. yield new IDSource() {
  338. final String zeroPad = Strings.format("%0" + TestUtil.nextInt(random, 4, 20) + "d", 0);
  339. int upto;
  340. @Override
  341. public String next() {
  342. String s = Integer.toString(upto++);
  343. return zeroPad.substring(zeroPad.length() - s.length()) + s;
  344. }
  345. };
  346. }
  347. case 4 -> {
  348. // random long
  349. logger.info("--> use random long ids");
  350. yield new IDSource() {
  351. final int radix = TestUtil.nextInt(random, Character.MIN_RADIX, Character.MAX_RADIX);
  352. @Override
  353. public String next() {
  354. return Long.toString(random.nextLong() & 0x3ffffffffffffffL, radix);
  355. }
  356. };
  357. }
  358. case 5 -> {
  359. // zero-pad random long
  360. logger.info("--> use zero-padded random long ids");
  361. yield new IDSource() {
  362. final int radix = TestUtil.nextInt(random, Character.MIN_RADIX, Character.MAX_RADIX);
  363. @Override
  364. public String next() {
  365. return Long.toString(random.nextLong() & 0x3ffffffffffffffL, radix);
  366. }
  367. };
  368. }
  369. default -> throw new AssertionError();
  370. };
  371. }
  372. private static class IDAndVersion {
  373. public String id;
  374. public long version;
  375. public boolean delete;
  376. public int threadID = -1;
  377. public long indexStartTime;
  378. public long indexFinishTime;
  379. public boolean versionConflict;
  380. public boolean alreadyExists;
  381. public ActionResponse response;
  382. @Override
  383. public String toString() {
  384. StringBuilder sb = new StringBuilder();
  385. sb.append("id=");
  386. sb.append(id);
  387. sb.append(" version=");
  388. sb.append(version);
  389. sb.append(" delete?=");
  390. sb.append(delete);
  391. sb.append(" threadID=");
  392. sb.append(threadID);
  393. sb.append(" indexStartTime=");
  394. sb.append(indexStartTime);
  395. sb.append(" indexFinishTime=");
  396. sb.append(indexFinishTime);
  397. sb.append(" versionConflict=");
  398. sb.append(versionConflict);
  399. sb.append(" alreadyExists?=");
  400. sb.append(alreadyExists);
  401. if (response != null) {
  402. if (response instanceof DeleteResponse deleteResponse) {
  403. sb.append(" response:");
  404. sb.append(" index=");
  405. sb.append(deleteResponse.getIndex());
  406. sb.append(" id=");
  407. sb.append(deleteResponse.getId());
  408. sb.append(" version=");
  409. sb.append(deleteResponse.getVersion());
  410. sb.append(" found=");
  411. sb.append(deleteResponse.getResult() == DocWriteResponse.Result.DELETED);
  412. } else if (response instanceof IndexResponse indexResponse) {
  413. sb.append(" index=");
  414. sb.append(indexResponse.getIndex());
  415. sb.append(" id=");
  416. sb.append(indexResponse.getId());
  417. sb.append(" version=");
  418. sb.append(indexResponse.getVersion());
  419. sb.append(" created=");
  420. sb.append(indexResponse.getResult() == DocWriteResponse.Result.CREATED);
  421. } else {
  422. sb.append(" response: " + response);
  423. }
  424. } else {
  425. sb.append(" response: null");
  426. }
  427. return sb.toString();
  428. }
  429. }
  430. public void testRandomIDsAndVersions() throws Exception {
  431. createIndex("test");
  432. ensureGreen();
  433. // TODO: sometimes use _bulk API
  434. // TODO: test non-aborting exceptions (Rob suggested field where positions overflow)
  435. // TODO: not great we don't test deletes GC here:
  436. // We test deletes, but can't rely on wall-clock delete GC:
  437. updateIndexSettings(Settings.builder().put("index.gc_deletes", "1000000h"), "test");
  438. Random random = random();
  439. // Generate random IDs:
  440. IDSource idSource = getRandomIDs();
  441. Set<String> idsSet = new HashSet<>();
  442. String idPrefix;
  443. if (randomBoolean()) {
  444. idPrefix = "";
  445. } else {
  446. idPrefix = TestUtil.randomSimpleString(random);
  447. logger.debug("--> use id prefix {}", idPrefix);
  448. }
  449. int numIDs;
  450. if (TEST_NIGHTLY) {
  451. numIDs = scaledRandomIntBetween(300, 1000);
  452. } else {
  453. numIDs = scaledRandomIntBetween(50, 100);
  454. }
  455. while (idsSet.size() < numIDs) {
  456. idsSet.add(idPrefix + idSource.next());
  457. }
  458. String[] ids = idsSet.toArray(new String[numIDs]);
  459. boolean useMonotonicVersion = randomBoolean();
  460. // Attach random versions to them:
  461. long version = 0;
  462. final IDAndVersion[] idVersions = new IDAndVersion[TestUtil.nextInt(random, numIDs / 2, numIDs * (TEST_NIGHTLY ? 8 : 2))];
  463. final Map<String, IDAndVersion> truth = new HashMap<>();
  464. logger.debug("--> use {} ids; {} operations", numIDs, idVersions.length);
  465. for (int i = 0; i < idVersions.length; i++) {
  466. if (useMonotonicVersion) {
  467. version += TestUtil.nextInt(random, 1, 10);
  468. } else {
  469. version = random.nextLong() & 0x3fffffffffffffffL;
  470. }
  471. idVersions[i] = new IDAndVersion();
  472. idVersions[i].id = ids[random.nextInt(numIDs)];
  473. idVersions[i].version = version;
  474. // 20% of the time we delete:
  475. idVersions[i].delete = random.nextInt(5) == 2;
  476. IDAndVersion curVersion = truth.get(idVersions[i].id);
  477. if (curVersion == null || idVersions[i].version > curVersion.version) {
  478. // Save highest version per id:
  479. truth.put(idVersions[i].id, idVersions[i]);
  480. }
  481. }
  482. // Shuffle
  483. for (int i = idVersions.length - 1; i > 0; i--) {
  484. int index = random.nextInt(i + 1);
  485. IDAndVersion x = idVersions[index];
  486. idVersions[index] = idVersions[i];
  487. idVersions[i] = x;
  488. }
  489. for (IDAndVersion idVersion : idVersions) {
  490. logger.debug(
  491. "--> id={} version={} delete?={} truth?={}",
  492. idVersion.id,
  493. idVersion.version,
  494. idVersion.delete,
  495. truth.get(idVersion.id) == idVersion
  496. );
  497. }
  498. final AtomicInteger upto = new AtomicInteger();
  499. final long startTime = System.nanoTime();
  500. startInParallel(TestUtil.nextInt(random, 1, TEST_NIGHTLY ? 20 : 5), threadID -> {
  501. try {
  502. // final Random threadRandom = RandomizedContext.current().getRandom();
  503. final Random threadRandom = random();
  504. while (true) {
  505. // TODO: sometimes use bulk:
  506. int index = upto.getAndIncrement();
  507. if (index >= idVersions.length) {
  508. break;
  509. }
  510. if (index % 100 == 0) {
  511. logger.trace("{}: index={}", Thread.currentThread().getName(), index);
  512. }
  513. IDAndVersion idVersion = idVersions[index];
  514. String id = idVersion.id;
  515. idVersion.threadID = threadID;
  516. idVersion.indexStartTime = System.nanoTime() - startTime;
  517. long v = idVersion.version;
  518. if (idVersion.delete) {
  519. try {
  520. idVersion.response = client().prepareDelete("test", id)
  521. .setVersion(v)
  522. .setVersionType(VersionType.EXTERNAL)
  523. .get();
  524. } catch (VersionConflictEngineException vcee) {
  525. // OK: our version is too old
  526. assertThat(v, lessThanOrEqualTo(truth.get(id).version));
  527. idVersion.versionConflict = true;
  528. }
  529. } else {
  530. try {
  531. idVersion.response = prepareIndex("test").setId(id)
  532. .setSource("foo", "bar")
  533. .setVersion(v)
  534. .setVersionType(VersionType.EXTERNAL)
  535. .get();
  536. } catch (VersionConflictEngineException vcee) {
  537. // OK: our version is too old
  538. assertThat(v, lessThanOrEqualTo(truth.get(id).version));
  539. idVersion.versionConflict = true;
  540. }
  541. }
  542. idVersion.indexFinishTime = System.nanoTime() - startTime;
  543. if (threadRandom.nextInt(100) == 7) {
  544. logger.trace("--> {}: TEST: now refresh at {}", threadID, System.nanoTime() - startTime);
  545. refresh();
  546. logger.trace("--> {}: TEST: refresh done at {}", threadID, System.nanoTime() - startTime);
  547. }
  548. if (threadRandom.nextInt(100) == 7) {
  549. logger.trace("--> {}: TEST: now flush at {}", threadID, System.nanoTime() - startTime);
  550. flush();
  551. logger.trace("--> {}: TEST: flush done at {}", threadID, System.nanoTime() - startTime);
  552. }
  553. }
  554. } catch (Exception e) {
  555. throw new RuntimeException(e);
  556. }
  557. });
  558. // Verify against truth:
  559. boolean failed = false;
  560. for (String id : ids) {
  561. long expected;
  562. IDAndVersion idVersion = truth.get(id);
  563. if (idVersion != null && idVersion.delete == false) {
  564. expected = idVersion.version;
  565. } else {
  566. expected = -1;
  567. }
  568. long actualVersion = client().prepareGet("test", id).get().getVersion();
  569. if (actualVersion != expected) {
  570. logger.error("--> FAILED: idVersion={} actualVersion= {}", idVersion, actualVersion);
  571. failed = true;
  572. }
  573. }
  574. if (failed) {
  575. StringBuilder sb = new StringBuilder();
  576. for (int i = 0; i < idVersions.length; i++) {
  577. sb.append("i=").append(i).append(" ").append(idVersions[i]).append(System.lineSeparator());
  578. }
  579. logger.error("All versions: {}", sb);
  580. fail("wrong versions for some IDs");
  581. }
  582. }
  583. public void testDeleteNotLost() throws Exception {
  584. // We require only one shard for this test, so that the 2nd delete provokes pruning the deletes map:
  585. indicesAdmin().prepareCreate("test").setSettings(Settings.builder().put("index.number_of_shards", 1)).get();
  586. ensureGreen();
  587. updateIndexSettings(Settings.builder().put("index.gc_deletes", "10ms").put("index.refresh_interval", "-1"), "test");
  588. // Index a doc:
  589. prepareIndex("test").setId("id")
  590. .setSource("foo", "bar")
  591. .setOpType(DocWriteRequest.OpType.INDEX)
  592. .setVersion(10)
  593. .setVersionType(VersionType.EXTERNAL)
  594. .get();
  595. if (randomBoolean()) {
  596. // Force refresh so the add is sometimes visible in the searcher:
  597. refresh();
  598. }
  599. // Delete it
  600. client().prepareDelete("test", "id").setVersion(11).setVersionType(VersionType.EXTERNAL).get();
  601. // Real-time get should reflect delete:
  602. assertThat("doc should have been deleted", client().prepareGet("test", "id").get().getVersion(), equalTo(-1L));
  603. // ThreadPool.relativeTimeInMillis has default granularity of 200 msec, so we must sleep at least that long; sleep much longer in
  604. // case system is busy:
  605. Thread.sleep(1000);
  606. // Delete an unrelated doc (provokes pruning deletes from versionMap)
  607. client().prepareDelete("test", "id2").setVersion(11).setVersionType(VersionType.EXTERNAL).get();
  608. // Real-time get should still reflect delete:
  609. assertThat("doc should have been deleted", client().prepareGet("test", "id").get().getVersion(), equalTo(-1L));
  610. }
  611. public void testGCDeletesZero() throws Exception {
  612. createIndex("test");
  613. ensureGreen();
  614. // We test deletes, but can't rely on wall-clock delete GC:
  615. updateIndexSettings(Settings.builder().put("index.gc_deletes", "0ms"), "test");
  616. // Index a doc:
  617. prepareIndex("test").setId("id")
  618. .setSource("foo", "bar")
  619. .setOpType(DocWriteRequest.OpType.INDEX)
  620. .setVersion(10)
  621. .setVersionType(VersionType.EXTERNAL)
  622. .get();
  623. if (randomBoolean()) {
  624. // Force refresh so the add is sometimes visible in the searcher:
  625. refresh();
  626. }
  627. // Delete it
  628. client().prepareDelete("test", "id").setVersion(11).setVersionType(VersionType.EXTERNAL).get();
  629. // Real-time get should reflect delete even though index.gc_deletes is 0:
  630. assertThat("doc should have been deleted", client().prepareGet("test", "id").get().getVersion(), equalTo(-1L));
  631. }
  632. public void testSpecialVersioning() {
  633. internalCluster().ensureAtLeastNumDataNodes(2);
  634. createIndex("test", Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0).build());
  635. DocWriteResponse doc1 = prepareIndex("test").setId("1")
  636. .setSource("field", "value1")
  637. .setVersion(0)
  638. .setVersionType(VersionType.EXTERNAL)
  639. .get();
  640. assertThat(doc1.getVersion(), equalTo(0L));
  641. DocWriteResponse doc2 = prepareIndex("test").setId("1")
  642. .setSource("field", "value2")
  643. .setVersion(Versions.MATCH_ANY)
  644. .setVersionType(VersionType.INTERNAL)
  645. .get();
  646. assertThat(doc2.getVersion(), equalTo(1L));
  647. client().prepareDelete("test", "1").get(); // v2
  648. DocWriteResponse doc3 = prepareIndex("test").setId("1")
  649. .setSource("field", "value3")
  650. .setVersion(Versions.MATCH_DELETED)
  651. .setVersionType(VersionType.INTERNAL)
  652. .get();
  653. assertThat(doc3.getVersion(), equalTo(3L));
  654. DocWriteResponse doc4 = prepareIndex("test").setId("1")
  655. .setSource("field", "value4")
  656. .setVersion(4L)
  657. .setVersionType(VersionType.EXTERNAL_GTE)
  658. .get();
  659. assertThat(doc4.getVersion(), equalTo(4L));
  660. // Make sure that these versions are replicated correctly
  661. setReplicaCount(1, "test");
  662. ensureGreen("test");
  663. }
  664. }