|
25 | 25 | import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper; |
26 | 26 | import org.apache.iotdb.it.framework.IoTDBTestRunner; |
27 | 27 | import org.apache.iotdb.itbase.category.DailyIT; |
| 28 | +import org.apache.iotdb.itbase.exception.InconsistentDataException; |
28 | 29 | import org.apache.iotdb.rpc.IoTDBConnectionException; |
29 | 30 | import org.apache.iotdb.rpc.StatementExecutionException; |
30 | 31 | import org.apache.iotdb.rpc.TSStatusCode; |
31 | 32 | import org.apache.iotdb.session.Session; |
32 | 33 |
|
| 34 | +import org.awaitility.Awaitility; |
33 | 35 | import org.junit.Test; |
34 | 36 | import org.junit.experimental.categories.Category; |
35 | 37 | import org.junit.runner.RunWith; |
@@ -64,22 +66,42 @@ public void testRepeatedlyRestartWholeClusterWithWrite() throws Exception { |
64 | 66 | testRepeatedlyRestartWholeCluster( |
65 | 67 | (s, i, env) -> { |
66 | 68 | if (i != 0) { |
67 | | - ResultSet resultSet = s.executeQuery("SELECT last s1 FROM root.**"); |
68 | | - ResultSetMetaData metaData = resultSet.getMetaData(); |
69 | | - assertEquals(4, metaData.getColumnCount()); |
70 | | - int cnt = 0; |
71 | | - while (resultSet.next()) { |
72 | | - cnt++; |
73 | | - StringBuilder result = new StringBuilder(); |
74 | | - for (int j = 0; j < metaData.getColumnCount(); j++) { |
75 | | - result |
76 | | - .append(metaData.getColumnName(j + 1)) |
77 | | - .append(":") |
78 | | - .append(resultSet.getString(j + 1)) |
79 | | - .append(","); |
80 | | - } |
81 | | - System.out.println(result); |
82 | | - } |
| 69 | + // This query is fanned out to every DataNode and the results are compared across |
| 70 | + // replicas. Right after a restart the last cache on each coordinator is reloaded |
| 71 | + // lazily, so the cross-replica comparison may transiently observe an inconsistent |
| 72 | + // result until the cluster converges. ORDER BY TIMESERIES makes the row order |
| 73 | + // deterministic across coordinators (the root cause of the observed flakiness), and the |
| 74 | + // retry tolerates the brief convergence window (e.g. a replica that has not finished |
| 75 | + // recovering yet) without masking a genuine, persistent inconsistency. |
| 76 | + // |
| 77 | + // ignoreExceptionsMatching(InconsistentDataException) is required: a mismatch surfaces |
| 78 | + // as InconsistentDataException (a RuntimeException) thrown from getString(), and |
| 79 | + // untilAsserted() only retries on AssertionError by default, so without it the retry |
| 80 | + // would not actually cover this failure. We match only InconsistentDataException so a |
| 81 | + // genuine error (e.g. a real SQLException) still fails fast instead of being retried. |
| 82 | + Awaitility.await() |
| 83 | + .atMost(60, TimeUnit.SECONDS) |
| 84 | + .pollInterval(2, TimeUnit.SECONDS) |
| 85 | + .ignoreExceptionsMatching(e -> e instanceof InconsistentDataException) |
| 86 | + .untilAsserted( |
| 87 | + () -> { |
| 88 | + try (ResultSet resultSet = |
| 89 | + s.executeQuery("SELECT last s1 FROM root.** ORDER BY TIMESERIES ASC")) { |
| 90 | + ResultSetMetaData metaData = resultSet.getMetaData(); |
| 91 | + assertEquals(4, metaData.getColumnCount()); |
| 92 | + while (resultSet.next()) { |
| 93 | + StringBuilder result = new StringBuilder(); |
| 94 | + for (int j = 0; j < metaData.getColumnCount(); j++) { |
| 95 | + result |
| 96 | + .append(metaData.getColumnName(j + 1)) |
| 97 | + .append(":") |
| 98 | + .append(resultSet.getString(j + 1)) |
| 99 | + .append(","); |
| 100 | + } |
| 101 | + System.out.println(result); |
| 102 | + } |
| 103 | + } |
| 104 | + }); |
83 | 105 | } |
84 | 106 | s.execute("INSERT INTO root.db1.d1 (time, s1) VALUES (1, 1)"); |
85 | 107 | s.execute("INSERT INTO root.db2.d1 (time, s1) VALUES (1, 1)"); |
|
0 commit comments