Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 105 additions & 15 deletions versions/scylla/3.11.4.0/patch
Original file line number Diff line number Diff line change
Expand Up @@ -760,30 +760,120 @@ index 777ea439f8..a63e169dac 100644
assertThat(table.getColumns().get(1))
.isNotNull()
diff --git a/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java b/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java
index b1867a0562..8fa0a885b9 100644
index b1867a0562..2825c44b12 100644
--- a/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java
+++ b/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java
@@ -82,12 +82,17 @@
event.getSource(),
event.getThreadName(),
event.getDescription());
@@ -17,6 +17,7 @@ package com.datastax.driver.core;

import static com.datastax.driver.core.Assertions.assertThat;
import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;

import com.datastax.driver.core.QueryTrace.Event;
import com.datastax.driver.core.utils.ScyllaOnly;
@@ -36,6 +37,13 @@ import org.testng.annotations.Test;
})
public class ShardAwarenessTest extends CCMTestsSupport {
private static final Logger logger = LoggerFactory.getLogger(ShardAwarenessTest.class);
+
+ /**
+ * How many times a traced read is re-issued when its query trace comes back without the local
+ * read event. Guards against incomplete traces, not against a wrong shard.
+ */
+ private static final int MAX_TRACE_ATTEMPTS = 3;
+
private final boolean useAdvancedShardAwareness;

@Factory(dataProvider = "dataProvider")
@@ -62,32 +70,63 @@ public class ShardAwarenessTest extends CCMTestsSupport {
private void verifyCorrectShardSingleRow(String pk, String ck, String v, String shard) {
PreparedStatement prepared =
session().prepare("SELECT pk, ck, v FROM shardawaretest.t WHERE pk=? AND ck=?");
- ResultSet result = session().execute(prepared.bind(pk, ck).enableTracing());
-
- Row row = result.one();
- assertTrue(result.isExhausted());
- assertThat(row).isNotNull();
- assertThat(row.getString("pk")).isEqualTo(pk);
- assertThat(row.getString("ck")).isEqualTo(ck);
- assertThat(row.getString("v")).isEqualTo(v);
-
- ExecutionInfo executionInfo = result.getExecutionInfo();
-
- QueryTrace trace = executionInfo.getQueryTrace();
- boolean anyLocal = false;
- for (Event event : trace.getEvents()) {
- logger.info(
- " {} - {} - [{}] - {}",
- event.getSourceElapsedMicros(),
- event.getSource(),
- event.getThreadName(),
- event.getDescription());
- assertThat(event.getThreadName()).startsWith(shard);
+ // Only data-local events carry the owning shard. Scylla 2025.1 adds coordinator-side
+ // events that legitimately run on shard 0 regardless of the data shard. Also, since
+ // Scylla 2025.1 the thread name carries a service-level suffix ("shard N/sl:<level>"),
+ // so strip it before comparing.
if (event.getDescription().contains("querying locally")) {
anyLocal = true;
+ String normalized = event.getThreadName().replaceFirst("/sl:[^/]*$", "");
+ assertThat(normalized).startsWith(shard);
- if (event.getDescription().contains("querying locally")) {
- anyLocal = true;
+
+ for (int attempt = 1; attempt <= MAX_TRACE_ATTEMPTS; attempt++) {
+ // Bind a fresh statement on every attempt. Reusing one BoundStatement across attempts
+ // would pin the coordinator through the paging optimization, which would silently void
+ // the shard assertion below.
+ ResultSet result = session().execute(prepared.bind(pk, ck).enableTracing());
+
+ Row row = result.one();
+ assertTrue(result.isExhausted());
+ assertThat(row).isNotNull();
+ assertThat(row.getString("pk")).isEqualTo(pk);
+ assertThat(row.getString("ck")).isEqualTo(ck);
+ assertThat(row.getString("v")).isEqualTo(v);
+
+ ExecutionInfo executionInfo = result.getExecutionInfo();
+
+ QueryTrace trace = executionInfo.getQueryTrace();
+ boolean anyLocal = false;
+ for (Event event : trace.getEvents()) {
+ logger.info(
+ " {} - {} - [{}] - {}",
+ event.getSourceElapsedMicros(),
+ event.getSource(),
+ event.getThreadName(),
+ event.getDescription());
+ // Only data-local events carry the owning shard. Scylla 2025.1 adds coordinator-side
+ // events that legitimately run on shard 0 regardless of the data shard. Also, since
+ // Scylla 2025.1 the thread name carries a service-level suffix ("shard N/sl:<level>"),
+ // so strip it before comparing.
+ if (event.getDescription().contains("querying locally")) {
+ anyLocal = true;
+ String normalized = event.getThreadName().replaceFirst("/sl:[^/]*$", "");
+ assertThat(normalized).startsWith(shard);
+ }
+ }
+ if (anyLocal) {
+ return;
}
+ // QueryTrace waits only until the coordinator's session row reports a duration; the
+ // events fetched alongside it can still be incomplete, and QueryTrace.doFetchTrace
+ // documents that the trace "may not contain the log of replicas". An event list without
+ // the local read is therefore not a functional failure, so retry with a fresh trace.
+ // Re-executing the query is the only way to get one: once a trace has been fetched its
+ // events are cached and frozen, so re-reading the same trace can never return more.
+ logger.warn(
+ "Attempt {}/{} for pk={}: trace {} carried no 'querying locally' event ({} events);"
+ + " retrying with a fresh trace",
+ attempt,
+ MAX_TRACE_ATTEMPTS,
+ pk,
+ trace.getTraceId(),
+ trace.getEvents().size());
}
- assertThat(anyLocal);
+ assertTrue(anyLocal, "No 'querying locally' trace event was observed for the query");
+ fail(
+ "No 'querying locally' trace event was observed for the query after "
+ + MAX_TRACE_ATTEMPTS
+ + " attempts");
}

@Test(groups = "short")
@@ -95,7 +100,7 @@ public class ShardAwarenessTest extends CCMTestsSupport {
@@ -95,7 +134,7 @@ public class ShardAwarenessTest extends CCMTestsSupport {
session().execute("DROP KEYSPACE IF EXISTS shardawaretest");
session()
.execute(
Expand Down
120 changes: 105 additions & 15 deletions versions/scylla/3.11.5.15/patch
Original file line number Diff line number Diff line change
Expand Up @@ -99,30 +99,120 @@ index ea75f84544..ea70e9081a 100644

private static final Logger logger = LoggerFactory.getLogger(SessionStressTest.class);
diff --git a/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java b/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java
index ac36feb518..8fa0a885b9 100644
index ac36feb518..2825c44b12 100644
--- a/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java
+++ b/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java
@@ -82,12 +82,17 @@
event.getSource(),
event.getThreadName(),
event.getDescription());
@@ -17,6 +17,7 @@ package com.datastax.driver.core;

import static com.datastax.driver.core.Assertions.assertThat;
import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;

import com.datastax.driver.core.QueryTrace.Event;
import com.datastax.driver.core.utils.ScyllaOnly;
@@ -36,6 +37,13 @@ import org.testng.annotations.Test;
})
public class ShardAwarenessTest extends CCMTestsSupport {
private static final Logger logger = LoggerFactory.getLogger(ShardAwarenessTest.class);
+
+ /**
+ * How many times a traced read is re-issued when its query trace comes back without the local
+ * read event. Guards against incomplete traces, not against a wrong shard.
+ */
+ private static final int MAX_TRACE_ATTEMPTS = 3;
+
private final boolean useAdvancedShardAwareness;

@Factory(dataProvider = "dataProvider")
@@ -62,32 +70,63 @@ public class ShardAwarenessTest extends CCMTestsSupport {
private void verifyCorrectShardSingleRow(String pk, String ck, String v, String shard) {
PreparedStatement prepared =
session().prepare("SELECT pk, ck, v FROM shardawaretest.t WHERE pk=? AND ck=?");
- ResultSet result = session().execute(prepared.bind(pk, ck).enableTracing());
-
- Row row = result.one();
- assertTrue(result.isExhausted());
- assertThat(row).isNotNull();
- assertThat(row.getString("pk")).isEqualTo(pk);
- assertThat(row.getString("ck")).isEqualTo(ck);
- assertThat(row.getString("v")).isEqualTo(v);
-
- ExecutionInfo executionInfo = result.getExecutionInfo();
-
- QueryTrace trace = executionInfo.getQueryTrace();
- boolean anyLocal = false;
- for (Event event : trace.getEvents()) {
- logger.info(
- " {} - {} - [{}] - {}",
- event.getSourceElapsedMicros(),
- event.getSource(),
- event.getThreadName(),
- event.getDescription());
- assertThat(event.getThreadName()).startsWith(shard);
+ // Only data-local events carry the owning shard. Scylla 2025.1 adds coordinator-side
+ // events that legitimately run on shard 0 regardless of the data shard. Also, since
+ // Scylla 2025.1 the thread name carries a service-level suffix ("shard N/sl:<level>"),
+ // so strip it before comparing.
if (event.getDescription().contains("querying locally")) {
anyLocal = true;
+ String normalized = event.getThreadName().replaceFirst("/sl:[^/]*$", "");
+ assertThat(normalized).startsWith(shard);
- if (event.getDescription().contains("querying locally")) {
- anyLocal = true;
+
+ for (int attempt = 1; attempt <= MAX_TRACE_ATTEMPTS; attempt++) {
+ // Bind a fresh statement on every attempt. Reusing one BoundStatement across attempts
+ // would pin the coordinator through the paging optimization, which would silently void
+ // the shard assertion below.
+ ResultSet result = session().execute(prepared.bind(pk, ck).enableTracing());
+
+ Row row = result.one();
+ assertTrue(result.isExhausted());
+ assertThat(row).isNotNull();
+ assertThat(row.getString("pk")).isEqualTo(pk);
+ assertThat(row.getString("ck")).isEqualTo(ck);
+ assertThat(row.getString("v")).isEqualTo(v);
+
+ ExecutionInfo executionInfo = result.getExecutionInfo();
+
+ QueryTrace trace = executionInfo.getQueryTrace();
+ boolean anyLocal = false;
+ for (Event event : trace.getEvents()) {
+ logger.info(
+ " {} - {} - [{}] - {}",
+ event.getSourceElapsedMicros(),
+ event.getSource(),
+ event.getThreadName(),
+ event.getDescription());
+ // Only data-local events carry the owning shard. Scylla 2025.1 adds coordinator-side
+ // events that legitimately run on shard 0 regardless of the data shard. Also, since
+ // Scylla 2025.1 the thread name carries a service-level suffix ("shard N/sl:<level>"),
+ // so strip it before comparing.
+ if (event.getDescription().contains("querying locally")) {
+ anyLocal = true;
+ String normalized = event.getThreadName().replaceFirst("/sl:[^/]*$", "");
+ assertThat(normalized).startsWith(shard);
+ }
+ }
+ if (anyLocal) {
+ return;
}
+ // QueryTrace waits only until the coordinator's session row reports a duration; the
+ // events fetched alongside it can still be incomplete, and QueryTrace.doFetchTrace
+ // documents that the trace "may not contain the log of replicas". An event list without
+ // the local read is therefore not a functional failure, so retry with a fresh trace.
+ // Re-executing the query is the only way to get one: once a trace has been fetched its
+ // events are cached and frozen, so re-reading the same trace can never return more.
+ logger.warn(
+ "Attempt {}/{} for pk={}: trace {} carried no 'querying locally' event ({} events);"
+ + " retrying with a fresh trace",
+ attempt,
+ MAX_TRACE_ATTEMPTS,
+ pk,
+ trace.getTraceId(),
+ trace.getEvents().size());
}
- assertThat(anyLocal);
+ assertTrue(anyLocal, "No 'querying locally' trace event was observed for the query");
+ fail(
+ "No 'querying locally' trace event was observed for the query after "
+ + MAX_TRACE_ATTEMPTS
+ + " attempts");
}

@Test(groups = "short")
@@ -95,7 +100,7 @@
@@ -95,7 +134,7 @@ public class ShardAwarenessTest extends CCMTestsSupport {
session().execute("DROP KEYSPACE IF EXISTS shardawaretest");
session()
.execute(
Expand Down
Loading