This is an automated email from the ASF dual-hosted git repository.

rohangarg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new eabce8a159 Fix flakiness in query-retry ITs (#12818)
eabce8a159 is described below

commit eabce8a1590d564702321fea98157289776e63c5
Author: Rohan Garg <[email protected]>
AuthorDate: Tue Aug 2 17:20:16 2022 +0530

    Fix flakiness in query-retry ITs (#12818)
---
 .../docker/docker-compose.query-retry-test.yml     |  11 +-
 .../ServerManagerForQueryErrorTest.java            |  23 ++--
 .../query/ITQueryRetryTestOnMissingSegments.java   | 132 ++++++++++-----------
 3 files changed, 77 insertions(+), 89 deletions(-)

diff --git a/integration-tests/docker/docker-compose.query-retry-test.yml 
b/integration-tests/docker/docker-compose.query-retry-test.yml
index fbaaf07250..adba343557 100644
--- a/integration-tests/docker/docker-compose.query-retry-test.yml
+++ b/integration-tests/docker/docker-compose.query-retry-test.yml
@@ -50,15 +50,6 @@ services:
       - druid-metadata-storage
       - druid-zookeeper-kafka
 
-  druid-historical:
-    extends:
-      file: docker-compose.base.yml
-      service: druid-historical
-    environment:
-      - DRUID_INTEGRATION_TEST_GROUP=${DRUID_INTEGRATION_TEST_GROUP}
-    depends_on:
-      - druid-zookeeper-kafka
-
   druid-broker:
     extends:
       file: docker-compose.base.yml
@@ -67,7 +58,7 @@ services:
       - DRUID_INTEGRATION_TEST_GROUP=${DRUID_INTEGRATION_TEST_GROUP}
     depends_on:
       - druid-zookeeper-kafka
-      - druid-historical
+      - druid-historical-for-query-retry-test
 
   druid-router:
     extends:
diff --git 
a/integration-tests/src/main/java/org/apache/druid/server/coordination/ServerManagerForQueryErrorTest.java
 
b/integration-tests/src/main/java/org/apache/druid/server/coordination/ServerManagerForQueryErrorTest.java
index 87c50c88c6..ec3ad43a73 100644
--- 
a/integration-tests/src/main/java/org/apache/druid/server/coordination/ServerManagerForQueryErrorTest.java
+++ 
b/integration-tests/src/main/java/org/apache/druid/server/coordination/ServerManagerForQueryErrorTest.java
@@ -51,9 +51,7 @@ import org.apache.druid.server.SegmentManager;
 import org.apache.druid.server.initialization.ServerConfig;
 import org.apache.druid.timeline.VersionedIntervalTimeline;
 
-import java.util.HashSet;
 import java.util.Optional;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Function;
@@ -63,8 +61,9 @@ import java.util.function.Function;
  *
  * - Missing segments. A segment can be missing during a query if a historical 
drops the segment
  *   after the broker issues the query to the historical. To mimic this 
situation, the historical
- *   with this server manager announces all segments assigned, but reports 
missing segments for the
- *   first 3 segments specified in the query. See 
ITQueryRetryTestOnMissingSegments.
+ *   with this server manager announces all segments assigned, but reports 
missing segment for the
+ *   first segment of the datasource specified in the query. The missing 
report is only generated once for the first
+ *   segment. Post that report, all segments are served for the datasource. 
See ITQueryRetryTestOnMissingSegments.
  * - Other query errors. This server manager returns a sequence that always 
throws an exception
  *   based on a given query context value. See ITQueryErrorTest.
  *
@@ -82,9 +81,9 @@ public class ServerManagerForQueryErrorTest extends 
ServerManager
   public static final String QUERY_FAILURE_TEST_CONTEXT_KEY = 
"query-failure-test";
 
   private static final Logger LOG = new 
Logger(ServerManagerForQueryErrorTest.class);
-  private static final int MAX_NUM_FALSE_MISSING_SEGMENTS_REPORTS = 3;
+  private static final int MAX_NUM_FALSE_MISSING_SEGMENTS_REPORTS = 1;
 
-  private final ConcurrentHashMap<String, Set<SegmentDescriptor>> 
queryToIgnoredSegments = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<String, Integer> queryToIgnoredSegments = 
new ConcurrentHashMap<>();
 
   @Inject
   public ServerManagerForQueryErrorTest(
@@ -130,15 +129,15 @@ public class ServerManagerForQueryErrorTest extends 
ServerManager
       final MutableBoolean isIgnoreSegment = new MutableBoolean(false);
       queryToIgnoredSegments.compute(
           query.getMostSpecificId(),
-          (queryId, ignoredSegments) -> {
-            if (ignoredSegments == null) {
-              ignoredSegments = new HashSet<>();
+          (queryId, ignoreCounter) -> {
+            if (ignoreCounter == null) {
+              ignoreCounter = 0;
             }
-            if (ignoredSegments.size() < 
MAX_NUM_FALSE_MISSING_SEGMENTS_REPORTS) {
-              ignoredSegments.add(descriptor);
+            if (ignoreCounter < MAX_NUM_FALSE_MISSING_SEGMENTS_REPORTS) {
+              ignoreCounter++;
               isIgnoreSegment.setTrue();
             }
-            return ignoredSegments;
+            return ignoreCounter;
           }
       );
 
diff --git 
a/integration-tests/src/test/java/org/apache/druid/tests/query/ITQueryRetryTestOnMissingSegments.java
 
b/integration-tests/src/test/java/org/apache/druid/tests/query/ITQueryRetryTestOnMissingSegments.java
index 73394c3f0e..7d8528b05c 100644
--- 
a/integration-tests/src/test/java/org/apache/druid/tests/query/ITQueryRetryTestOnMissingSegments.java
+++ 
b/integration-tests/src/test/java/org/apache/druid/tests/query/ITQueryRetryTestOnMissingSegments.java
@@ -51,8 +51,8 @@ import java.util.Map;
 /**
  * This class tests the query retry on missing segments. A segment can be 
missing in a historical during a query if
  * the historical drops the segment after the broker issues the query to the 
historical. To mimic this case, this
- * test spawns two historicals, a normal historical and a historical modified 
for testing. The later historical
- * announces all segments assigned, but doesn't serve all of them. Instead, it 
can report missing segments for some
+ * test spawns a historical modified for testing. This historical announces 
all segments assigned, but doesn't serve
+ * all of them always. Instead, it can report missing segments for some
  * segments. See {@link ServerManagerForQueryErrorTest} for more details.
  * <p>
  * To run this test properly, the test group must be specified as {@link 
TestNGGroup#QUERY_RETRY}.
@@ -63,25 +63,22 @@ public class ITQueryRetryTestOnMissingSegments
 {
   private static final String WIKIPEDIA_DATA_SOURCE = "wikipedia_editstream";
   private static final String QUERIES_RESOURCE = 
"/queries/wikipedia_editstream_queries_query_retry_test.json";
-  private static final int TIMES_TO_RUN = 50;
 
   /**
-   * This test runs the same query multiple times. This enumeration represents 
an expectation after finishing
-   * running the query.
+   * This enumeration represents an expectation after finishing running the 
test query.
    */
   private enum Expectation
   {
     /**
-     * Expect that all runs succeed.
+     * Expect that the test query succeed and with correct results.
      */
     ALL_SUCCESS,
     /**
-     * Expect that all runs returns the 200 HTTP response, but some of them 
can return incorrect result.
+     * Expect that the test query returns the 200 HTTP response, but will 
surely return incorrect result.
      */
     INCORRECT_RESULT,
     /**
-     * Expect that some runs can return the 500 HTTP response. For the runs 
returned the 200 HTTP response, the query
-     * result must be correct.
+     * Expect that the test query must return the 500 HTTP response.
      */
     QUERY_FAILURE
   }
@@ -100,7 +97,7 @@ public class ITQueryRetryTestOnMissingSegments
   @BeforeMethod
   public void before()
   {
-    // ensure that wikipedia segments are loaded completely
+    // ensure that wikipedia segment is loaded completely
     ITRetryUtil.retryUntilTrue(
         () -> coordinatorClient.areSegmentsLoaded(WIKIPEDIA_DATA_SOURCE), 
"wikipedia segment load"
     );
@@ -109,25 +106,26 @@ public class ITQueryRetryTestOnMissingSegments
   @Test
   public void testWithRetriesDisabledPartialResultDisallowed() throws Exception
   {
-    // Since retry is disabled and partial result is not allowed, we can 
expect some queries can fail.
-    // If a query succeed, its result must be correct.
+    // Since retry is disabled and partial result is not allowed, the query 
must fail.
     testQueries(buildQuery(0, false), Expectation.QUERY_FAILURE);
   }
 
   @Test
   public void testWithRetriesDisabledPartialResultAllowed() throws Exception
   {
-    // Since retry is disabled but partial result is allowed, all queries must 
succeed.
-    // However, some queries can return incorrect result.
+    // Since retry is disabled but partial result is allowed, the query must 
succeed.
+    // However, the query must return incorrect result.
     testQueries(buildQuery(0, true), Expectation.INCORRECT_RESULT);
   }
 
   @Test
   public void testWithRetriesEnabledPartialResultDisallowed() throws Exception
   {
-    // Since retry is enabled, all queries must succeed even though partial 
result is disallowed.
-    // All queries must return correct result.
-    testQueries(buildQuery(30, false), Expectation.ALL_SUCCESS);
+    // Since retry is enabled, the query must succeed even though partial 
result is disallowed.
+    // The retry count is set to 1 since on the first retry of the query (i.e 
second overall try), the historical
+    // will start processing the segment and not call it missing.
+    // The query must return correct results.
+    testQueries(buildQuery(1, false), Expectation.ALL_SUCCESS);
   }
 
   private void testQueries(String queryWithResultsStr, Expectation 
expectation) throws Exception
@@ -147,74 +145,73 @@ public class ITQueryRetryTestOnMissingSegments
     int queryFailure = 0;
     int resultMatches = 0;
     int resultMismatches = 0;
-    for (int i = 0; i < TIMES_TO_RUN; i++) {
-      for (QueryWithResults queryWithResult : queries) {
-        final StatusResponseHolder responseHolder = queryClient
-            .queryAsync(queryHelper.getQueryURL(config.getBrokerUrl()), 
queryWithResult.getQuery())
-            .get();
-
-        if (responseHolder.getStatus().getCode() == 
HttpResponseStatus.OK.getCode()) {
-          querySuccess++;
-
-          List<Map<String, Object>> result = jsonMapper.readValue(
-              responseHolder.getContent(),
-              new TypeReference<List<Map<String, Object>>>()
-              {
-              }
-          );
-          if (!QueryResultVerifier.compareResults(
-              result,
-              queryWithResult.getExpectedResults(),
-              queryWithResult.getFieldsToTest()
-          )) {
-            if (expectation != Expectation.INCORRECT_RESULT) {
-              throw new ISE(
-                  "Incorrect query results for query %s \n expectedResults: %s 
\n actualResults : %s",
-                  queryWithResult.getQuery(),
-                  
jsonMapper.writeValueAsString(queryWithResult.getExpectedResults()),
-                  jsonMapper.writeValueAsString(result)
-              );
-            } else {
-              resultMismatches++;
+
+    for (QueryWithResults queryWithResult : queries) {
+      final StatusResponseHolder responseHolder = queryClient
+          .queryAsync(queryHelper.getQueryURL(config.getBrokerUrl()), 
queryWithResult.getQuery())
+          .get();
+
+      if (responseHolder.getStatus().getCode() == 
HttpResponseStatus.OK.getCode()) {
+        querySuccess++;
+
+        List<Map<String, Object>> result = jsonMapper.readValue(
+            responseHolder.getContent(),
+            new TypeReference<List<Map<String, Object>>>()
+            {
             }
+        );
+        if (!QueryResultVerifier.compareResults(
+            result,
+            queryWithResult.getExpectedResults(),
+            queryWithResult.getFieldsToTest()
+        )) {
+          if (expectation != Expectation.INCORRECT_RESULT) {
+            throw new ISE(
+                "Incorrect query results for query %s \n expectedResults: %s 
\n actualResults : %s",
+                queryWithResult.getQuery(),
+                
jsonMapper.writeValueAsString(queryWithResult.getExpectedResults()),
+                jsonMapper.writeValueAsString(result)
+            );
           } else {
-            resultMatches++;
+            resultMismatches++;
           }
-        } else if (responseHolder.getStatus().getCode() == 
HttpResponseStatus.INTERNAL_SERVER_ERROR.getCode() &&
-                   expectation == Expectation.QUERY_FAILURE) {
-          final Map<String, Object> response = 
jsonMapper.readValue(responseHolder.getContent(), Map.class);
-          final String errorMessage = (String) response.get("errorMessage");
-          Assert.assertNotNull(errorMessage, "errorMessage");
-          Assert.assertTrue(errorMessage.contains("No results found for 
segments"));
-          queryFailure++;
         } else {
-          throw new ISE(
-              "Unexpected failure, code: [%s], content: [%s]",
-              responseHolder.getStatus(),
-              responseHolder.getContent()
-          );
+          resultMatches++;
         }
+      } else if (responseHolder.getStatus().getCode() == 
HttpResponseStatus.INTERNAL_SERVER_ERROR.getCode() &&
+                 expectation == Expectation.QUERY_FAILURE) {
+        final Map<String, Object> response = 
jsonMapper.readValue(responseHolder.getContent(), Map.class);
+        final String errorMessage = (String) response.get("errorMessage");
+        Assert.assertNotNull(errorMessage, "errorMessage");
+        Assert.assertTrue(errorMessage.contains("No results found for 
segments"));
+        queryFailure++;
+      } else {
+        throw new ISE(
+            "Unexpected failure, code: [%s], content: [%s]",
+            responseHolder.getStatus(),
+            responseHolder.getContent()
+        );
       }
     }
 
     switch (expectation) {
       case ALL_SUCCESS:
-        Assert.assertEquals(querySuccess, 
ITQueryRetryTestOnMissingSegments.TIMES_TO_RUN);
+        Assert.assertEquals(querySuccess, 1);
         Assert.assertEquals(queryFailure, 0);
-        Assert.assertEquals(resultMatches, 
ITQueryRetryTestOnMissingSegments.TIMES_TO_RUN);
+        Assert.assertEquals(resultMatches, 1);
         Assert.assertEquals(resultMismatches, 0);
         break;
       case QUERY_FAILURE:
-        Assert.assertTrue(querySuccess > 0, "At least one query is expected to 
succeed.");
-        Assert.assertTrue(queryFailure > 0, "At least one query is expected to 
fail.");
-        Assert.assertEquals(querySuccess, resultMatches);
+        Assert.assertEquals(querySuccess, 0);
+        Assert.assertEquals(queryFailure, 1);
+        Assert.assertEquals(resultMatches, 0);
         Assert.assertEquals(resultMismatches, 0);
         break;
       case INCORRECT_RESULT:
-        Assert.assertEquals(querySuccess, 
ITQueryRetryTestOnMissingSegments.TIMES_TO_RUN);
+        Assert.assertEquals(querySuccess, 1);
         Assert.assertEquals(queryFailure, 0);
-        Assert.assertTrue(resultMatches > 0, "At least one query is expected 
to return correct results.");
-        Assert.assertTrue(resultMismatches > 0, "At least one query is 
expected to return less results.");
+        Assert.assertEquals(resultMatches, 0);
+        Assert.assertEquals(resultMismatches, 1);
         break;
       default:
         throw new ISE("Unknown expectation[%s]", expectation);
@@ -235,6 +232,7 @@ public class ITQueryRetryTestOnMissingSegments
     final Map<String, Object> context = new HashMap<>();
     // Disable cache so that each run hits historical.
     context.put(QueryContexts.USE_CACHE_KEY, false);
+    context.put(QueryContexts.USE_RESULT_LEVEL_CACHE_KEY, false);
     context.put(QueryContexts.NUM_RETRIES_ON_MISSING_SEGMENTS_KEY, 
numRetriesOnMissingSegments);
     context.put(QueryContexts.RETURN_PARTIAL_RESULTS_KEY, allowPartialResults);
     context.put(ServerManagerForQueryErrorTest.QUERY_RETRY_TEST_CONTEXT_KEY, 
true);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to