Skip to main content

Overview

The RetryPolicy trait defines how the driver decides whether and how to retry failed requests. Policies can retry on the same node, retry on a different node, fail immediately, or ignore write errors.
Most users should use the DefaultRetryPolicy which implements sensible retry behavior.
Source: scylla/src/policies/retry/retry_policy.rs:37

Trait Definition

pub trait RetryPolicy: std::fmt::Debug + Send + Sync {
    fn new_session(&self) -> Box<dyn RetrySession>;
}

Method

new_session

Creates a new retry session for a single request.
fn new_session(&self) -> Box<dyn RetrySession>
session
Box<dyn RetrySession>
A new retry session that will decide retry behavior for a request
Each request gets its own RetrySession to track retry state.

RetrySession

Tracks retry state for a single request and makes retry decisions.
pub trait RetrySession: Send + Sync {
    fn decide_should_retry(&mut self, request_info: RequestInfo) -> RetryDecision;
    
    fn reset(&mut self);
}

Methods

decide_should_retry

Decides what to do after a request failure.
fn decide_should_retry(&mut self, request_info: RequestInfo) -> RetryDecision
request_info
RequestInfo
required
Information about the failed request
decision
RetryDecision
How to handle the failure

reset

Resets the session state before reusing it for a new request.
fn reset(&mut self)

Supporting Types

RequestInfo

Information about a failed request.
pub struct RequestInfo<'a> {
    pub error: &'a RequestAttemptError,
    pub is_idempotent: bool,
    pub consistency: Consistency,
}
error
&RequestAttemptError
The error that caused the request to fail
is_idempotent
bool
Whether the request is safe to retry (can be applied multiple times)
consistency
Consistency
The consistency level of the failed request

RetryDecision

Instructs the driver how to handle a failed request.
pub enum RetryDecision {
    RetrySameTarget(Option<Consistency>),
    RetryNextTarget(Option<Consistency>),
    DontRetry,
    IgnoreWriteError,
}
RetrySameTarget
RetryDecision
Retry on the same node and shard. Optional consistency overrides the original.
RetryNextTarget
RetryDecision
Retry on the next node from the load balancing plan. Optional consistency overrides the original.
DontRetry
RetryDecision
Stop retrying and return the error to the user.
IgnoreWriteError
RetryDecision
Return an empty successful response, ignoring the error.
Some(consistency) overrides the request consistency. None keeps the same consistency.

DefaultRetryPolicy

The default retry policy with sensible retry behavior.
pub struct DefaultRetryPolicy {
    // ... internal fields
}

Creation

use scylla::policies::retry::DefaultRetryPolicy;
use std::sync::Arc;

let policy = Arc::new(DefaultRetryPolicy::new());

Behavior

  • Read Timeout: Retry if enough replicas responded
  • Write Timeout:
    • Retry unlogged batch on same node
    • Retry other writes on next node
  • Unavailable: Retry on next node
  • Idempotence: Only retry idempotent requests on network errors
Example:
use scylla::{SessionBuilder, policies::retry::DefaultRetryPolicy};
use std::sync::Arc;

let policy = Arc::new(DefaultRetryPolicy::new());

let session = SessionBuilder::new()
    .known_node("127.0.0.1:9042")
    .retry_policy(policy)
    .build()
    .await?;

FallthroughRetryPolicy

Never retries - immediately returns errors to the user.
pub struct FallthroughRetryPolicy;

Creation

use scylla::policies::retry::FallthroughRetryPolicy;
use std::sync::Arc;

let policy = Arc::new(FallthroughRetryPolicy);
Example:
let session = SessionBuilder::new()
    .known_node("127.0.0.1:9042")
    .retry_policy(Arc::new(FallthroughRetryPolicy))
    .build()
    .await?;

DowngradingConsistencyRetryPolicy

Retries with lower consistency levels when possible.
pub struct DowngradingConsistencyRetryPolicy;
This policy can return partial results. Use with caution and only when appropriate for your application.

Creation

use scylla::policies::retry::DowngradingConsistencyRetryPolicy;
use std::sync::Arc;

let policy = Arc::new(DowngradingConsistencyRetryPolicy);

Behavior

  • Read Timeout: Retry with lower consistency if some replicas responded
  • Write Timeout: Retry with lower consistency for batch log writes
  • Unavailable: Retry with lower consistency on next node
Example:
let session = SessionBuilder::new()
    .known_node("127.0.0.1:9042")
    .retry_policy(Arc::new(DowngradingConsistencyRetryPolicy))
    .build()
    .await?;

Examples

Using DefaultRetryPolicy

use scylla::{Session, SessionBuilder};
use scylla::policies::retry::DefaultRetryPolicy;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let policy = Arc::new(DefaultRetryPolicy::new());
    
    let session = SessionBuilder::new()
        .known_node("127.0.0.1:9042")
        .retry_policy(policy)
        .build()
        .await?;

    // Requests will be retried according to DefaultRetryPolicy
    let result = session
        .query_unpaged("SELECT * FROM my_table", &[])
        .await?;
    
    Ok(())
}

Per-Statement Retry Policy

use scylla::statement::Statement;
use scylla::policies::retry::FallthroughRetryPolicy;
use std::sync::Arc;

let mut stmt = Statement::new("SELECT * FROM my_table");
stmt.set_retry_policy(Some(Arc::new(FallthroughRetryPolicy)));

// This query will not retry on failure
let result = session.query_unpaged(stmt, &[]).await?;

Custom Retry Policy

use scylla::policies::retry::{
    RetryPolicy, RetrySession, RetryDecision, RequestInfo
};
use scylla::frame::types::Consistency;
use scylla::errors::RequestAttemptError;

#[derive(Debug)]
struct LimitedRetryPolicy {
    max_retries: usize,
}

impl LimitedRetryPolicy {
    fn new(max_retries: usize) -> Self {
        Self { max_retries }
    }
}

impl RetryPolicy for LimitedRetryPolicy {
    fn new_session(&self) -> Box<dyn RetrySession> {
        Box::new(LimitedRetrySession {
            max_retries: self.max_retries,
            retry_count: 0,
        })
    }
}

struct LimitedRetrySession {
    max_retries: usize,
    retry_count: usize,
}

impl RetrySession for LimitedRetrySession {
    fn decide_should_retry(&mut self, request_info: RequestInfo) -> RetryDecision {
        if self.retry_count >= self.max_retries {
            return RetryDecision::DontRetry;
        }
        
        self.retry_count += 1;
        
        // Only retry on network errors for idempotent requests
        match request_info.error {
            RequestAttemptError::BrokenConnectionError(_) 
            | RequestAttemptError::UnableToAllocStreamId => {
                if request_info.is_idempotent {
                    RetryDecision::RetryNextTarget(None)
                } else {
                    RetryDecision::DontRetry
                }
            }
            _ => RetryDecision::DontRetry,
        }
    }

    fn reset(&mut self) {
        self.retry_count = 0;
    }
}

// Usage
let policy = Arc::new(LimitedRetryPolicy::new(3));
let session = SessionBuilder::new()
    .known_node("127.0.0.1:9042")
    .retry_policy(policy)
    .build()
    .await?;

Idempotence and Retries

use scylla::statement::Statement;

// Non-idempotent operation (won't retry on network errors)
let mut insert = Statement::new(
    "INSERT INTO counters (id) VALUES (?)"
);
insert.set_is_idempotent(false);

// Idempotent operation (safe to retry)
let mut select = Statement::new(
    "SELECT * FROM users WHERE id = ?"
);
select.set_is_idempotent(true);

// UPDATE with idempotent values
let mut update = Statement::new(
    "UPDATE users SET name = ? WHERE id = ?"
);
update.set_is_idempotent(true);  // Safe because we're setting to specific value

// Counter UPDATE is NOT idempotent
let mut counter = Statement::new(
    "UPDATE counters SET count = count + 1 WHERE id = ?"
);
counter.set_is_idempotent(false);  // Not safe to retry

Best Practices

  1. Use DefaultRetryPolicy for most applications
  2. Mark statements as idempotent when safe to retry
  3. Be cautious with DowngradingConsistencyRetryPolicy - it can return partial results
  4. Set per-statement policies for operations with special requirements
  5. Test retry behavior under failure scenarios
Idempotence is critical: Only retry idempotent operations on network errors. Non-idempotent operations (like counter increments) should not be retried automatically.

Retry Decision Guide

Error TypeDefaultRetryPolicyRecommendation
Read timeout (enough replicas)Retry same node✅ Safe
Read timeout (not enough)Don’t retry✅ Correct
Write timeout (batch log)Retry next node✅ Safe
Write timeout (unlogged)Retry same node✅ Safe
UnavailableRetry next node✅ Safe
Network error (idempotent)Retry next node✅ Safe
Network error (non-idempotent)Don’t retry✅ Correct

See Also

Build docs developers (and LLMs) love