The ScyllaDB Rust Driver allows you to configure client-side timeouts for individual statements, providing control over how long the driver waits for responses.
Overview
Timeouts prevent your application from waiting indefinitely for slow or unresponsive database operations. The driver supports two levels of timeout configuration:
- Statement-level timeouts: Override the default timeout for specific statements
- Profile-level timeouts: Set default timeouts in execution profiles
When a timeout occurs, the driver stops waiting for the response and returns an error.
Timeouts are client-side only. The server may continue processing the request even after the client times out.
Setting Timeouts on Statements
Unprepared Statements
use scylla::statement::unprepared::Statement;
use std::time::Duration;
let mut statement = Statement::new("SELECT * FROM ks.large_table");
// Set a 30-second timeout
statement.set_request_timeout(Some(Duration::from_secs(30)));
let result = session.query_unpaged(&statement, &[]).await?;
Prepared Statements
use scylla::statement::prepared::PreparedStatement;
use std::time::Duration;
let mut prepared = session
.prepare("SELECT * FROM ks.table WHERE id = ?")
.await?;
// Set a 10-second timeout
prepared.set_request_timeout(Some(Duration::from_secs(10)));
let result = session.execute_unpaged(&prepared, (1_i32,)).await?;
Batch Statements
use scylla::statement::batch::Batch;
use std::time::Duration;
let mut batch: Batch = Default::default();
// Batches may take longer, set higher timeout
batch.set_request_timeout(Some(Duration::from_secs(60)));
batch.append_statement("INSERT INTO ks.table (id, value) VALUES (?, ?)");
// ... add more statements
let result = session.batch(&batch, batch_values).await?;
Getting Timeout Values
Retrieve the configured timeout for a statement:
let mut statement = Statement::new("SELECT * FROM ks.table");
statement.set_request_timeout(Some(Duration::from_secs(30)));
if let Some(timeout) = statement.get_request_timeout() {
println!("Statement timeout: {:?}", timeout);
}
Unsetting Timeouts
To revert to the execution profile’s default timeout:
let mut statement = Statement::new("SELECT * FROM ks.table");
statement.set_request_timeout(Some(Duration::from_secs(30)));
// Revert to profile default
statement.set_request_timeout(None);
Timeout Hierarchy
Timeouts are resolved in the following order:
- Statement-level timeout: If set via
set_request_timeout(Some(duration))
- Execution profile timeout: From the associated execution profile
- Default profile timeout: From the session’s default execution profile
use scylla::client::execution_profile::ExecutionProfile;
use std::time::Duration;
// Create profile with default timeout
let profile = ExecutionProfile::builder()
.request_timeout(Some(Duration::from_secs(10)))
.build();
let profile_handle = session.add_execution_profile("slow_queries", profile);
// Statement inherits profile timeout
let mut statement = Statement::new("SELECT * FROM ks.table");
statement.set_execution_profile_handle(Some(profile_handle));
// Override with statement-specific timeout
statement.set_request_timeout(Some(Duration::from_secs(30)));
Handling Timeout Errors
use scylla::errors::ExecutionError;
use std::time::Duration;
let mut statement = Statement::new("SELECT * FROM ks.large_table");
statement.set_request_timeout(Some(Duration::from_secs(5)));
match session.query_unpaged(&statement, &[]).await {
Ok(result) => {
println!("Query succeeded");
}
Err(ExecutionError::RequestTimeout(_)) => {
eprintln!("Query timed out after 5 seconds");
// Handle timeout - maybe retry with longer timeout
}
Err(e) => {
eprintln!("Other error: {}", e);
}
}
Common Timeout Scenarios
Short Timeouts for Point Reads
use std::time::Duration;
let mut get_user = session
.prepare("SELECT * FROM ks.users WHERE id = ?")
.await?;
// Point reads should be fast
get_user.set_request_timeout(Some(Duration::from_millis(100)));
let result = session.execute_unpaged(&get_user, (user_id,)).await?;
Long Timeouts for Analytics
use std::time::Duration;
let mut analytics_query = Statement::new(
"SELECT COUNT(*) FROM ks.events WHERE date >= ? AND date < ?"
);
// Analytics queries may take longer
analytics_query.set_request_timeout(Some(Duration::from_secs(300)));
let result = session.query_unpaged(&analytics_query, (start_date, end_date)).await?;
Different Timeouts for LWT
use scylla::statement::SerialConsistency;
use std::time::Duration;
let mut lwt_statement = session
.prepare("UPDATE ks.accounts SET balance = ? WHERE id = ? IF balance >= ?")
.await?;
lwt_statement.set_serial_consistency(Some(SerialConsistency::LocalSerial));
// LWT operations need more time due to Paxos rounds
lwt_statement.set_request_timeout(Some(Duration::from_secs(20)));
let result = session
.execute_unpaged(&lwt_statement, (new_balance, account_id, required_balance))
.await?;
Paged Queries
For paged queries, the timeout applies to each page fetch:
use futures::stream::StreamExt;
use std::time::Duration;
let mut statement = Statement::new("SELECT * FROM ks.large_table")
.with_page_size(1000);
// Each page fetch has this timeout
statement.set_request_timeout(Some(Duration::from_secs(10)));
let mut rows_stream = session
.query_iter(statement, &[])
.await?
.rows_stream::<(i32, String)>()?;
// Each next() call that fetches a new page respects the timeout
while let Some((id, value)) = rows_stream.try_next().await? {
println!("id: {}, value: {}", id, value);
}
Session-Level Default Timeouts
Set default timeouts when building the session:
use scylla::client::session_builder::SessionBuilder;
use scylla::client::execution_profile::ExecutionProfile;
use std::time::Duration;
// Create default profile with timeout
let profile = ExecutionProfile::builder()
.request_timeout(Some(Duration::from_secs(30)))
.build();
let session = SessionBuilder::new()
.known_node("127.0.0.1:9042")
.default_execution_profile_handle(profile.into_handle())
.build()
.await?;
// All statements use 30s timeout unless overridden
Complete Example
use scylla::client::session::{Session, SessionBuilder};
use scylla::client::execution_profile::ExecutionProfile;
use scylla::statement::unprepared::Statement;
use scylla::statement::prepared::PreparedStatement;
use scylla::errors::ExecutionError;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create session with default 10-second timeout
let default_profile = ExecutionProfile::builder()
.request_timeout(Some(Duration::from_secs(10)))
.build();
let session: Session = SessionBuilder::new()
.known_node("127.0.0.1:9042")
.default_execution_profile_handle(default_profile.into_handle())
.build()
.await?;
session
.query_unpaged(
"CREATE KEYSPACE IF NOT EXISTS examples_ks \
WITH REPLICATION = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}",
&[],
)
.await?;
session
.query_unpaged(
"CREATE TABLE IF NOT EXISTS examples_ks.data \
(id int PRIMARY KEY, value text)",
&[],
)
.await?;
// Fast query with short timeout
let mut fast_query = session
.prepare("SELECT * FROM examples_ks.data WHERE id = ?")
.await?;
fast_query.set_request_timeout(Some(Duration::from_millis(100)));
match session.execute_unpaged(&fast_query, (1_i32,)).await {
Ok(result) => println!("Fast query succeeded"),
Err(ExecutionError::RequestTimeout(_)) => {
eprintln!("Fast query timed out");
}
Err(e) => eprintln!("Error: {}", e),
}
// Slow query with long timeout
let mut slow_query = Statement::new(
"SELECT * FROM examples_ks.data"
);
slow_query.set_request_timeout(Some(Duration::from_secs(60)));
match session.query_unpaged(&slow_query, &[]).await {
Ok(result) => {
let rows_result = result.into_rows_result()?;
println!("Slow query returned {} rows", rows_result.rows_num());
}
Err(ExecutionError::RequestTimeout(_)) => {
eprintln!("Slow query timed out after 60 seconds");
}
Err(e) => eprintln!("Error: {}", e),
}
// Statement using default profile timeout (10 seconds)
let result = session
.query_unpaged("SELECT * FROM examples_ks.data LIMIT 10", &[])
.await?;
println!("Query with default timeout succeeded");
Ok(())
}
Best Practices
Set Reasonable Defaults
Configure appropriate default timeouts in your execution profiles based on your typical query latency.
Override for Specific Cases
Use statement-level timeouts for queries that deviate significantly from the norm (very fast point reads or slow analytics).
Account for Network Latency
Remember that timeouts include network round-trip time, not just server processing time.
Test Timeout Values
Monitor query latencies in production and adjust timeouts based on actual performance characteristics.
Consider Retry Logic
Implement retry logic for timeout errors when appropriate, possibly with exponential backoff.
Common Pitfalls
Too short timeouts: Setting timeouts too low can cause spurious failures. Allow enough time for normal query execution plus network latency.
No timeout handling: Always handle timeout errors gracefully. A timeout doesn’t necessarily mean the operation failed on the server.
Ignoring page timeouts: In paged queries, each page fetch has the timeout applied. For large result sets, consider the total time across all pages.
See Also